¿Reemplazos para la declaración de cambio en Python?

1718

Quiero escribir una función en Python que devuelva diferentes valores fijos basados ​​en el valor de un índice de entrada.

En otros idiomas, usaría una declaración switcho case, pero Python no parece tener una switchdeclaración. ¿Cuáles son las soluciones Python recomendadas en este escenario?

Michael Schneider
fuente
77
PEP relacionada, creada por el propio Guido: PEP 3103
chb
28
@chb En ese PEP, Guido no menciona que las cadenas if / elif también son una fuente clásica de error. Es una construcción muy frágil.
itsbruce
15
Falta en todas las soluciones aquí la detección de valores de casos duplicados . Como principio de falla rápida, esta puede ser una pérdida más importante que el rendimiento o la característica de falla.
Bob Stein
66
switchen realidad es más "versátil" que algo que devuelve diferentes valores fijos en función del valor de un índice de entrada. Permite ejecutar diferentes piezas de código. En realidad, ni siquiera necesita devolver un valor. Me pregunto si algunas de las respuestas aquí son buenos reemplazos para una switchdeclaración general , o solo para el caso de devolver valores sin posibilidad de ejecutar partes generales de código.
sancho.s ReinstateMonicaCellio
3
@ MalikA.Rumi Construcción frágil, del mismo modo que un bucle while es una construcción frágil si intentas usarla para hacer lo que para ... en ... hace. ¿Vas a llamar a los programadores débiles por usar for loops? Si bien los bucles son todo lo que realmente necesitan. Pero para que los bucles muestren una intención clara, guarde repeticiones sin sentido y brinde la oportunidad de crear poderosas abstracciones.
itsbruce

Respuestas:

1486

Podrías usar un diccionario:

def f(x):
    return {
        'a': 1,
        'b': 2,
    }[x]
Greg Hewgill
fuente
100
¿Qué sucede si no se encuentra x?
Nick
46
@nick: puedes usar defaultdict
Eli Bendersky
385
Recomiendo poner el dict fuera de la función si el rendimiento es un problema, por lo que no reconstruye el dict en cada llamada a la función
Claudiu
56
@EliBendersky, Usar el getmétodo probablemente sería más normal que usar un collections.defaultdicten este caso.
Mike Graham
27
@ Nick, se produce una excepción; }.get(x, default)en su lugar, hazlo si debería haber un valor predeterminado. (Nota: ¡esto es mucho mejor de lo que sucede si deja el valor predeterminado en una declaración de cambio!)
Mike Graham
1374

Si desea valores predeterminados, puede utilizar el get(key[, default])método del diccionario :

def f(x):
    return {
        'a': 1,
        'b': 2
    }.get(x, 9)    # 9 is default if x not found
Mella
fuente
11
¿Qué pasa si 'a' y 'b' coinciden con 1, y 'c' y 'd' coinciden con 2?
John Mee el
13
@JM: Bueno, obviamente las búsquedas en el diccionario no admiten fallos. Podrías hacer una búsqueda de doble diccionario. Es decir, 'a' y 'b' apuntan a la respuesta1 y 'c' y 'd' apuntan a la respuesta2, que se encuentran en un segundo diccionario.
Nick
3
es mejor pasar un valor predeterminado
HaTiMSuM
Hay un problema con este enfoque, primero cada vez que llame si va a crear el dict nuevamente, segundo, si tiene un valor más complejo, puede obtener excepciones, por ejemplo. si x es una tupla y queremos hacer algo como esto x = ('a') def f (x): return {'a': x [0], 'b': x [1]} .get ( x [0], 9) Esto elevará IndexError
Idan Haim Shalom
2
@Idan: La pregunta era replicar el interruptor. Estoy seguro de que también podría romper este código si intentara poner valores impares. Sí, recreará, pero es fácil de arreglar.
Nick
394

Siempre me ha gustado hacerlo de esta manera

result = {
  'a': lambda x: x * 5,
  'b': lambda x: x + 7,
  'c': lambda x: x - 2
}[value](x)

De aquí

Mark Biek
fuente
16
mi mejor opción también es combinar con get () para manejar el valor predeterminado
drAlberT
27
tal vez no sea una buena idea usar lambda en este caso porque realmente se llama a lambda cada vez que se construye el diccionario.
Asher
13
Lamentablemente, esto es lo más cercano que van a llegar las personas. Los métodos que utilizan .get()(como las respuestas más altas actuales) deberán evaluar con entusiasmo todas las posibilidades antes de enviarlos, y por lo tanto no solo son (no solo muy, sino) extremadamente ineficientes y tampoco pueden tener efectos secundarios; esta respuesta evita ese problema, pero es más detallada. Simplemente usaría if / elif / else, e incluso esos tardan tanto tiempo en escribirse como 'case'.
ninjagecko
13
¿No evaluaría esto todas las funciones / lambdas cada vez en todos los casos, incluso si solo está devolviendo uno de los resultados?
slf
23
@slf No, cuando el flujo de control alcanza ese fragmento de código, generará 3 funciones (mediante el uso de las 3 lambdas) y luego creará un diccionario con esas 3 funciones como valores, pero permanecerán sin invocar ( evaluar es ligeramente ambiguo en ese contexto) al principio. Luego, el diccionario se indexa mediante [value], que devolverá solo una de las 3 funciones (suponiendo que valuesea ​​una de las 3 teclas). La función aún no se ha llamado en ese momento. Luego (x)llama a la función recién devuelta con xcomo argumento (y el resultado va a result). Las otras 2 funciones no serán llamadas.
blubberdiblub
354

Además de los métodos de diccionario (que realmente me gustan, por cierto), también puede usar if- elif- elsepara obtener la funcionalidad switch/ case/ default:

if x == 'a':
    # Do the thing
elif x == 'b':
    # Do the other thing
if x in 'bc':
    # Fall-through by not using elif, but now the default case includes case 'a'!
elif x in 'xyz':
    # Do yet another thing
else:
    # Do the default

Esto, por supuesto, no es idéntico al cambio / caso: no puede tener una falla tan fácil como omitir la breakdeclaración, pero puede tener una prueba más complicada. Su formato es más agradable que una serie de ifs anidados , aunque funcionalmente es a lo que está más cerca.

Matthew Schinckel
fuente
51
Realmente preferiría esto, usa una construcción de lenguaje estándar y no arroja un KeyError si no se encuentra un caso coincidente
Martyglaubitz
77
Pensé en el diccionario / getforma, pero la forma estándar es simplemente más legible.
Martin Thoma
2
@someuser pero el hecho de que puedan "superponerse" es una característica. Solo asegúrate de que el orden sea la prioridad en la que deberían ocurrir las coincidencias. En cuanto a x repetido: solo haz un x = the.other.thingantes. Por lo general, tendría un solo if, múltiples elif y un solo más, ya que es más fácil de entender.
Matthew Schinckel
77
Agradable, sin embargo, el "Fall-through al no usar elif" es un poco confuso. ¿Qué pasa con esto: olvidarse de "caer" y simplemente aceptarlo como dos if/elif/else?
Alois Mahdal
77
También vale la pena mencionar, al usar cosas como x in 'bc', tenga en cuenta que "" in "bc"es True.
Lohmar ASHAR
185

Mi receta favorita de Python para switch / case es:

choices = {'a': 1, 'b': 2}
result = choices.get(key, 'default')

Corto y simple para escenarios simples.

Compare con más de 11 líneas de código C:

// C Language version of a simple 'switch/case'.
switch( key ) 
{
    case 'a' :
        result = 1;
        break;
    case 'b' :
        result = 2;
        break;
    default :
        result = -1;
}

Incluso puede asignar múltiples variables usando tuplas:

choices = {'a': (1, 2, 3), 'b': (4, 5, 6)}
(result1, result2, result3) = choices.get(key, ('default1', 'default2', 'default3'))
ChaimG
fuente
16
Creo que esta es una respuesta más sólida que la aceptada.
Cerd
3
@some user: C requiere que el valor de retorno sea del mismo tipo para todos los casos. Python no lo hace. Quería resaltar esta flexibilidad de Python en caso de que alguien tuviera una situación que justificara tal uso.
ChaimG
3
@algunos usuarios: Personalmente, encuentro {} .get (,) legible. Para una mayor legibilidad para principiantes de Python, es posible que desee utilizar default = -1; result = choices.get(key, default).
ChaimG
44
comparar con 1 línea de c ++result=key=='a'?1:key==b?2:-1
Jasen
44
@Jasen se puede argumentar que se puede hacer en una sola línea de Python, así: result = 1 if key == 'a' else (2 if key == 'b' else 'default'). pero, ¿es legible el revestimiento?
ChaimG
101
class switch(object):
    value = None
    def __new__(class_, value):
        class_.value = value
        return True

def case(*args):
    return any((arg == switch.value for arg in args))

Uso:

while switch(n):
    if case(0):
        print "You typed zero."
        break
    if case(1, 4, 9):
        print "n is a perfect square."
        break
    if case(2):
        print "n is an even number."
    if case(2, 3, 5, 7):
        print "n is a prime number."
        break
    if case(6, 8):
        print "n is an even number."
        break
    print "Only single-digit numbers are allowed."
    break

Pruebas:

n = 2
#Result:
#n is an even number.
#n is a prime number.
n = 11
#Result:
#Only single-digit numbers are allowed.
adamh
fuente
64
Esto no es una amenaza segura. Si se golpean varios interruptores al mismo tiempo, todos los interruptores toman el valor del último interruptor.
francescortiz
48
Si bien es probable que @francescortiz sea seguro para subprocesos, tampoco es seguro para amenazas. ¡Amenaza los valores de las variables!
Zizouz212
77
El problema de seguridad de subprocesos probablemente podría solucionarse utilizando el almacenamiento local de subprocesos . O podría evitarse por completo devolviendo una instancia y utilizando esa instancia para las comparaciones de casos.
blubberdiblub
66
@blubberdiblub Pero entonces, ¿no es más eficiente usar una ifdeclaración estándar ?
wizzwizz4
99
Esto tampoco es seguro si se usa en múltiples funciones. En el ejemplo dado, si el case(2)bloque llamó a otra función que usa switch (), entonces al hacer case(2, 3, 5, 7)etc. para buscar el siguiente caso a ejecutar, usará el valor de cambio establecido por la otra función, no el establecido por la instrucción de cambio actual .
user9876
52

Mi favorita es una muy buena receta . Realmente te gustará. Es el más cercano que he visto a las declaraciones de casos de cambio reales, especialmente en las características.

class switch(object):
    def __init__(self, value):
        self.value = value
        self.fall = False

    def __iter__(self):
        """Return the match method once, then stop"""
        yield self.match
        raise StopIteration

    def match(self, *args):
        """Indicate whether or not to enter a case suite"""
        if self.fall or not args:
            return True
        elif self.value in args: # changed for v1.5, see below
            self.fall = True
            return True
        else:
            return False

Aquí hay un ejemplo:

# The following example is pretty much the exact use-case of a dictionary,
# but is included for its simplicity. Note that you can include statements
# in each suite.
v = 'ten'
for case in switch(v):
    if case('one'):
        print 1
        break
    if case('two'):
        print 2
        break
    if case('ten'):
        print 10
        break
    if case('eleven'):
        print 11
        break
    if case(): # default, could also just omit condition or 'if True'
        print "something else!"
        # No need to break here, it'll stop anyway

# break is used here to look as much like the real thing as possible, but
# elif is generally just as good and more concise.

# Empty suites are considered syntax errors, so intentional fall-throughs
# should contain 'pass'
c = 'z'
for case in switch(c):
    if case('a'): pass # only necessary if the rest of the suite is empty
    if case('b'): pass
    # ...
    if case('y'): pass
    if case('z'):
        print "c is lowercase!"
        break
    if case('A'): pass
    # ...
    if case('Z'):
        print "c is uppercase!"
        break
    if case(): # default
        print "I dunno what c was!"

# As suggested by Pierre Quentel, you can even expand upon the
# functionality of the classic 'case' statement by matching multiple
# cases in a single shot. This greatly benefits operations such as the
# uppercase/lowercase example above:
import string
c = 'A'
for case in switch(c):
    if case(*string.lowercase): # note the * for unpacking as arguments
        print "c is lowercase!"
        break
    if case(*string.uppercase):
        print "c is uppercase!"
        break
    if case('!', '?', '.'): # normal argument passing style also applies
        print "c is a sentence terminator!"
        break
    if case(): # default
        print "I dunno what c was!"
John Doe
fuente
3
Sustituiría for case in switch()por with switch() as case, tiene más sentido, ya que solo necesita ejecutarse una vez.
Ski
44
@Skirmantas: Tenga en cuenta que eso withno lo permite break, por lo que la opción de eliminación se elimina.
Jonas Schäfer
55
Disculpas por no poner más esfuerzo en determinar esto yo mismo: una respuesta similar anterior no es segura para subprocesos. ¿Es esto?
David Winiecki
1
@DavidWiniecki Los componentes del código que faltan en lo anterior (y posiblemente los derechos de autor de activestate) parecen ser seguros para subprocesos.
Jasen
sería otra versión de esto algo así if c in set(range(0,9)): print "digit" elif c in set(map(chr, range(ord('a'), ord('z')))): print "lowercase"?
mpag
51
class Switch:
    def __init__(self, value):
        self.value = value

    def __enter__(self):
        return self

    def __exit__(self, type, value, traceback):
        return False # Allows a traceback to occur

    def __call__(self, *values):
        return self.value in values


from datetime import datetime

with Switch(datetime.today().weekday()) as case:
    if case(0):
        # Basic usage of switch
        print("I hate mondays so much.")
        # Note there is no break needed here
    elif case(1,2):
        # This switch also supports multiple conditions (in one line)
        print("When is the weekend going to be here?")
    elif case(3,4):
        print("The weekend is near.")
    else:
        # Default would occur here
        print("Let's go have fun!") # Didn't use case for example purposes
Ian Bell
fuente
99
Usar gestores de contexto es una buena solución creativa. Recomiendo agregar un poco de explicación y tal vez un enlace a información sobre los administradores de contexto para darle a esta publicación algo de contexto;)
Será
2
No me gusta mucho si / elif encadena, pero esta es la más creativa y la más práctica de todas las soluciones que he visto usando la sintaxis existente de Python.
itsbruce
2
Esto es realmente lindo Una mejora sugerida es agregar una valuepropiedad (pública) a la clase Switch para que pueda hacer referencia case.valuea la declaración.
Peter
48

Hay un patrón que aprendí del código Twisted Python.

class SMTP:
    def lookupMethod(self, command):
        return getattr(self, 'do_' + command.upper(), None)
    def do_HELO(self, rest):
        return 'Howdy ' + rest
    def do_QUIT(self, rest):
        return 'Bye'

SMTP().lookupMethod('HELO')('foo.bar.com') # => 'Howdy foo.bar.com'
SMTP().lookupMethod('QUIT')('') # => 'Bye'

Puede usarlo en cualquier momento que necesite enviar un token y ejecutar un código extendido. En una máquina de estado tendrías state_métodos y despacho self.state. Este modificador puede ampliarse limpiamente heredando de la clase base y definiendo sus propios do_métodos. Muchas veces ni siquiera tendrás do_métodos en la clase base.

Editar: ¿cómo se usa exactamente eso?

En caso de SMTP, recibirá HELOde la transferencia. El código relevante (de twisted/mail/smtp.py, modificado para nuestro caso) se ve así

class SMTP:
    # ...

    def do_UNKNOWN(self, rest):
        raise NotImplementedError, 'received unknown command'

    def state_COMMAND(self, line):
        line = line.strip()
        parts = line.split(None, 1)
        if parts:
            method = self.lookupMethod(parts[0]) or self.do_UNKNOWN
            if len(parts) == 2:
                return method(parts[1])
            else:
                return method('')
        else:
            raise SyntaxError, 'bad syntax'

SMTP().state_COMMAND('   HELO   foo.bar.com  ') # => Howdy foo.bar.com

Recibirás ' HELO foo.bar.com '(o podrías recibir 'QUIT'o 'RCPT TO: foo'). Esto se tokeniza en partscomo ['HELO', 'foo.bar.com']. Se toma el nombre de búsqueda del método real parts[0].

(También se llama al método original state_COMMAND, porque usa el mismo patrón para implementar una máquina de estados, es decir getattr(self, 'state_' + self.mode))


fuente
44
No veo el beneficio de este patrón sobre solo llamar a los métodos directamente: SMTP (). Do_HELO ('foo.bar.com') OK, puede haber un código común en el método de búsqueda, pero dado que eso también puede sobrescribirse la subclase no veo lo que obtienes de la indirección.
Mr Shark el
1
No sabría qué método llamar por adelantado, es decir, 'HELO' proviene de una variable. He agregado un ejemplo de uso a la publicación original
¿Puedo sugerir simplemente: eval ('SMTP (). Do_' + comando) ('foo.bar.com')
jforberg
8
eval? ¿seriamente? y en lugar de crear una instancia de un método por llamada, podemos crear una instancia muy bien una vez y usarlo en todas las llamadas siempre que no tenga un estado interno.
Mahesh
1
En mi opinión, la clave real aquí es el envío utilizando getattr para especificar una función para ejecutar. Si los métodos estuvieran en un módulo, podría hacer getattr (locals (), func_name) para obtenerlo. La parte 'do_' es buena para la seguridad / errores, por lo que solo se pueden llamar funcs con el prefijo. SMTP mismo llama a lookupMethod. Idealmente, el exterior no sabe nada de esto. Realmente no tiene sentido hacer SMTP (). LookupMethod (nombre) (datos). Dado que el comando y los datos están en una cadena y SMTP lo analiza, eso tiene más sentido. Por último, SMTP probablemente tiene otro estado compartido que justifica que sea una clase.
ShawnFumo
27

Digamos que no solo desea devolver un valor, sino que desea utilizar métodos que cambien algo en un objeto. Usar el enfoque aquí indicado sería:

result = {
  'a': obj.increment(x),
  'b': obj.decrement(x)
}.get(value, obj.default(x))

Lo que sucede aquí es que python evalúa todos los métodos en el diccionario. Entonces, incluso si su valor es 'a', el objeto se incrementará y disminuirá en x.

Solución:

func, args = {
  'a' : (obj.increment, (x,)),
  'b' : (obj.decrement, (x,)),
}.get(value, (obj.default, (x,)))

result = func(*args)

Entonces obtienes una lista que contiene una función y sus argumentos. De esta manera, solo se devuelve el puntero de función y la lista de argumentos, no se evalúa. 'resultado' luego evalúa la llamada de función devuelta.

GeeF
fuente
23

Voy a dejar caer mis dos centavos aquí. La razón por la que no hay una declaración de caso / cambio en Python es porque Python sigue el principio de "Hay una sola forma correcta de hacer algo". Entonces, obviamente, podría encontrar varias formas de recrear la funcionalidad del interruptor / caso, pero la forma pitónica de lograr esto es la construcción if / elif. es decir

if something:
    return "first thing"
elif somethingelse:
    return "second thing"
elif yetanotherthing:
    return "third thing"
else:
    return "default thing"

Sentí que PEP 8 merecía un guiño aquí. Una de las cosas hermosas de Python es su simplicidad y elegancia. Eso se deriva en gran medida de los principios establecidos en PEP 8, que incluyen "Solo hay una manera correcta de hacer algo"

usuario2233949
fuente
66
Entonces, ¿por qué Python tiene bucles for y while? Todo lo que puede hacer con un ciclo for lo puede implementar con un ciclo while.
itsbruce
1
Cierto. Los programadores principiantes abusan con demasiada frecuencia del interruptor / caso Lo que realmente quieren es el patrón de estrategia .
user228395
Parece que Python desea que fuera Clojure
TWR Cole
1
@TWRCole No lo creo, Python lo estaba haciendo primero. Python ha existido desde 1990 y Clojure desde 2007.
Taylor
Solo hay una manera correcta de hacer algo. Python 2.7 o Python 3? Jajaja
TWR Cole el
17

ampliando la idea de "dictar como interruptor". si desea usar un valor predeterminado para su conmutador:

def f(x):
    try:
        return {
            'a': 1,
            'b': 2,
        }[x]
    except KeyError:
        return 'default'
Jeremy Cantrell
fuente
14
Creo que es más claro usar .get () en el dict con el valor predeterminado especificado. Prefiero dejar Excepciones por circunstancias excepcionales, y corta tres líneas de código y un nivel de sangría sin ser oscuro.
Chris B.
10
Esta es una circunstancia excepcional. Puede o no ser una circunstancia rara dependiendo de lo útil, pero definitivamente es una excepción (recurrir 'default') de la regla (obtener algo de este dict). Por diseño, los programas Python usan excepciones en un abrir y cerrar de ojos. Dicho esto, el uso getpodría hacer que el código sea un poco más agradable.
Mike Graham
16

Si tiene un bloque de casos complicado, puede considerar usar una tabla de búsqueda de diccionario de funciones ...

Si no ha hecho esto antes, es una buena idea ingresar a su depurador y ver exactamente cómo el diccionario busca cada función.

NOTA: No use "()" dentro de la caja / búsqueda de diccionario o se llamará a cada una de sus funciones como se crea el bloque de diccionario / caso. Recuerde esto porque solo desea llamar a cada función una vez usando una búsqueda de estilo hash.

def first_case():
    print "first"

def second_case():
    print "second"

def third_case():
    print "third"

mycase = {
'first': first_case, #do not use ()
'second': second_case, #do not use ()
'third': third_case #do not use ()
}
myfunc = mycase['first']
myfunc()
Asher
fuente
Me gusta tu solución Pero, ¿qué pasa si solo necesito pasar algunas variables u objetos?
Tedo Vrbanec
Esto no funcionará si el método espera parámetros.
Kulasangar
16

Si está buscando una declaración adicional, como "interruptor", construí un módulo de Python que extiende Python. Se llama ESPY como "Estructura mejorada para Python" y está disponible para Python 2.xy Python 3.x.

Por ejemplo, en este caso, el siguiente código podría realizar una declaración de cambio:

macro switch(arg1):
    while True:
        cont=False
        val=%arg1%
        socket case(arg2):
            if val==%arg2% or cont:
                cont=True
                socket
        socket else:
            socket
        break

que se puede usar así:

a=3
switch(a):
    case(0):
        print("Zero")
    case(1):
        print("Smaller than 2"):
        break
    else:
        print ("greater than 1")

así que espy traducirlo en Python como:

a=3
while True:
    cont=False
    if a==0 or cont:
        cont=True
        print ("Zero")
    if a==1 or cont:
        cont=True
        print ("Smaller than 2")
        break
    print ("greater than 1")
    break
elp
fuente
Muy bien, pero ¿cuál es el punto while True:en la parte superior del código Python generado? Inevitablemente llegará breaka la parte inferior del código Python generado, por lo que me parece que tanto el while True:y como breakpodrían eliminarse. Además, ¿ESPY es lo suficientemente inteligente como para cambiar el nombre de contsi el usuario usa ese mismo nombre en su propio código? En cualquier caso, quiero usar Python vainilla para no usar esto, pero no obstante es genial. +1 por pura frescura.
ArtOfWarfare
@ArtOfWarfare La razón de la while True:y breakes la de permitir, pero no requerir de paso al siguiente.
Solomon Ucko
¿Este módulo aún está disponible?
Solomon Ucko
15

Encontré que una estructura de interruptor común:

switch ...parameter...
case p1: v1; break;
case p2: v2; break;
default: v3;

se puede expresar en Python de la siguiente manera:

(lambda x: v1 if p1(x) else v2 if p2(x) else v3)

o formateado de una manera más clara:

(lambda x:
     v1 if p1(x) else
     v2 if p2(x) else
     v3)

En lugar de ser una declaración, la versión de Python es una expresión, que se evalúa como un valor.

León
fuente
También en lugar de ... parámetro ... y p1 (x) ¿qué tal parameteryp1==parameter
Bob Stein
@ BobStein-VisiBone hola, aquí es un ejemplo que se ejecuta en mi sesión de pitón: f = lambda x: 'a' if x==0 else 'b' if x==1 else 'c'. Cuando más tarde llamé f(2), recibí 'c'; f(1), 'b'; y f(0), 'a'. En cuanto a p1 (x), denota un predicado; siempre que regrese Trueo False, no importa si es una llamada a una función o una expresión, está bien.
leo
@ BobStein-VisiBone Sí, tienes razón! Gracias :) Para que la expresión de varias líneas funcione, se deben colocar paréntesis, como en su sugerencia o en mi ejemplo modificado.
leo
Excelente. Ahora eliminaré todos mis comentarios sobre los padres.
Bob Stein
15

La mayoría de las respuestas aquí son bastante antiguas, y especialmente las aceptadas, por lo que parece que vale la pena actualizarlas.

Primero, las preguntas frecuentes oficiales de Python cubren esto, y recomiendan la elifcadena para casos simples y dictpara casos más grandes o más complejos. También sugiere un conjunto de visit_métodos (un estilo utilizado por muchos marcos de servidores) para algunos casos:

def dispatch(self, value):
    method_name = 'visit_' + str(value)
    method = getattr(self, method_name)
    method()

Las preguntas frecuentes también mencionan PEP 275 , que fue escrito para obtener una decisión oficial de una vez por todas sobre la adición de declaraciones de cambio de estilo C. Pero esa PEP se aplazó a Python 3, y solo se rechazó oficialmente como una propuesta separada, PEP 3103 . La respuesta fue, por supuesto, no, pero las dos PEP tienen enlaces a información adicional si está interesado en los motivos o el historial.


Una cosa que surgió varias veces (y se puede ver en PEP 275, a pesar de que se recortó como una recomendación real) es que si realmente le molesta tener 8 líneas de código para manejar 4 casos, frente a los 6 líneas que tendrías en C o Bash, siempre puedes escribir esto:

if x == 1: print('first')
elif x == 2: print('second')
elif x == 3: print('third')
else: print('did not place')

Esto no es exactamente alentado por PEP 8, pero es legible y no demasiado unidiomático.


Durante más de una década desde que se rechazó el PEP 3103, el tema de las declaraciones de casos de estilo C, o incluso la versión un poco más poderosa en Go, se ha considerado muerto; cada vez que alguien lo menciona en python-ideas o -dev, se les remite a la antigua decisión.

Sin embargo, la idea de la coincidencia completa de patrones de estilo ML surge cada pocos años, especialmente desde que lenguajes como Swift y Rust lo han adoptado. El problema es que es difícil aprovechar la coincidencia de patrones sin tipos de datos algebraicos. Si bien Guido ha simpatizado con la idea, a nadie se le ocurrió una propuesta que se adapte muy bien a Python. (Puede leer mi Strawman 2014 para ver un ejemplo.) Esto podría cambiar con dataclass3.7 y algunas propuestas esporádicas para un enumtipo de suma más potente para manejar, o con varias propuestas para diferentes tipos de enlaces de declaración local (como PEP 3150 , o el conjunto de propuestas actualmente en discusión sobre -ideas). Pero hasta ahora, no lo ha hecho.

También hay ocasionalmente propuestas para la coincidencia de estilo Perl 6, que es básicamente una mezcla de todo, desde elifexpresiones regulares hasta cambio de tipo de despacho único.

abarnert
fuente
15

Solución para ejecutar funciones:

result = {
    'case1':     foo1, 
    'case2':     foo2,
    'case3':     foo3,
    'default':   default,
}.get(option)()

donde foo1 (), foo2 (), foo3 () y default () son funciones

Alejandro Quintanar
fuente
1
Sí, por ejemplo, si su opción variable == "case2" su resultado = foo2 ()
Alejandro Quintanar
y tal y tal.
Alejandro Quintanar
Sí, entiendo el propósito. Pero mi preocupación es que si solo deseas foo2(), el foo1(), foo3()ydefault() las funciones están también van a ejecutar, es decir, las cosas podrían tomar un largo tiempo
Brian Underwood
1
omita el () dentro del diccionario. uso get(option)(). problema resuelto.
timgeb
1
Excelente el uso de () es una solución de rejilla, hice un resumen
Alejandro Quintanar
13

No encontré la respuesta simple que estaba buscando en ninguna parte de la búsqueda de Google. Pero lo descubrí de todos modos. Es realmente bastante simple. Decidí publicarlo, y tal vez evitar algunos rasguños menos en la cabeza de otra persona. La clave es simplemente "in" y tuplas. Aquí está el comportamiento de la instrucción switch con fall-through, incluido el fall-through RANDOM.

l = ['Dog', 'Cat', 'Bird', 'Bigfoot',
     'Dragonfly', 'Snake', 'Bat', 'Loch Ness Monster']

for x in l:
    if x in ('Dog', 'Cat'):
        x += " has four legs"
    elif x in ('Bat', 'Bird', 'Dragonfly'):
        x += " has wings."
    elif x in ('Snake',):
        x += " has a forked tongue."
    else:
        x += " is a big mystery by default."
    print(x)

print()

for x in range(10):
    if x in (0, 1):
        x = "Values 0 and 1 caught here."
    elif x in (2,):
        x = "Value 2 caught here."
    elif x in (3, 7, 8):
        x = "Values 3, 7, 8 caught here."
    elif x in (4, 6):
        x = "Values 4 and 6 caught here"
    else:
        x = "Values 5 and 9 caught in default."
    print(x)

Proporciona:

Dog has four legs
Cat has four legs
Bird has wings.
Bigfoot is a big mystery by default.
Dragonfly has wings.
Snake has a forked tongue.
Bat has wings.
Loch Ness Monster is a big mystery by default.

Values 0 and 1 caught here.
Values 0 and 1 caught here.
Value 2 caught here.
Values 3, 7, 8 caught here.
Values 4 and 6 caught here
Values 5 and 9 caught in default.
Values 4 and 6 caught here
Values 3, 7, 8 caught here.
Values 3, 7, 8 caught here.
Values 5 and 9 caught in default.
JD Graham
fuente
¿Dónde exactamente se está cayendo aquí?
Jonas Schäfer
¡Uy! Hay una caída allí, pero ya no estoy contribuyendo a Stack Overflow. No me gustan en absoluto. Me gustan las contribuciones de otros, pero no Stackoverflow. Si está utilizando fall-through para FUNCIONALIDAD, entonces desea CATAR ciertas condiciones en una declaración de caso en un interruptor (un atrapar todo), hasta que llegue a una declaración de interrupción en un interruptor.
JD Graham
2
Aquí, tanto los valores "Dog" como "Cat" FALL THROUGH y son manejados por la MISMA funcionalidad, es decir, se definen como que tienen "cuatro patas". Es un ABSTRACTO equivalente a caer y diferentes valores manejados por la MISMA declaración de caso donde ocurre una ruptura.
JD Graham
@JDGraham Creo que Jonas significó otro aspecto del fracaso, que ocurre cuando el programador ocasionalmente olvida escribir breakal final del código para a case. Pero creo que no necesitamos ese "fracaso" :)
Mikhail Batcer
12

Las soluciones que uso:

Una combinación de 2 de las soluciones publicadas aquí, que es relativamente fácil de leer y admite valores predeterminados.

result = {
  'a': lambda x: x * 5,
  'b': lambda x: x + 7,
  'c': lambda x: x - 2
}.get(whatToUse, lambda x: x - 22)(value)

dónde

.get('c', lambda x: x - 22)(23)

mira "lambda x: x - 2"en el dict y lo usa conx=23

.get('xxx', lambda x: x - 22)(44)

no lo encuentra en el dict y usa el valor predeterminado "lambda x: x - 22"con x=44.

thomasf1
fuente
10
# simple case alternative

some_value = 5.0

# this while loop block simulates a case block

# case
while True:

    # case 1
    if some_value > 5:
        print ('Greater than five')
        break

    # case 2
    if some_value == 5:
        print ('Equal to five')
        break

    # else case 3
    print ( 'Must be less than 5')
    break
usuario5224656
fuente
10
def f(x):
    dictionary = {'a':1, 'b':2, 'c':3}
    return dictionary.get(x,'Not Found') 
##Returns the value for the letter x;returns 'Not Found' if x isn't a key in the dictionary
Vikhyat Agarwal
fuente
Considere incluir una breve descripción de su código y cómo resuelve la pregunta publicada
Henry Woody
Bien, agregué un comentario para eso ahora.
Vikhyat Agarwal
8

Me gustó la respuesta de Mark Bies

Como la xvariable debe usarse dos veces, modifiqué las funciones lambda a sin parámetros.

Tengo que correr con results[value](value)

In [2]: result = {
    ...:   'a': lambda x: 'A',
    ...:   'b': lambda x: 'B',
    ...:   'c': lambda x: 'C'
    ...: }
    ...: result['a']('a')
    ...: 
Out[2]: 'A'

In [3]: result = {
    ...:   'a': lambda : 'A',
    ...:   'b': lambda : 'B',
    ...:   'c': lambda : 'C',
    ...:   None: lambda : 'Nothing else matters'

    ...: }
    ...: result['a']()
    ...: 
Out[3]: 'A'

Editar: Me di cuenta de que puedo usar el Nonetipo con diccionarios. Entonces esto emularíaswitch ; case else

guneysus
fuente
¿El caso None no emula simplemente result[None]()?
Bob Stein
Sí exactamente. Quiero decirresult = {'a': 100, None:5000}; result[None]
guneysus
44
Simplemente comprobar que nadie está pensando se None:comporta como default:.
Bob Stein
7
def f(x):
     return 1 if x == 'a' else\
            2 if x in 'bcd' else\
            0 #default

Corto y fácil de leer, tiene un valor predeterminado y admite expresiones en ambas condiciones y valores de retorno.

Sin embargo, es menos eficiente que la solución con un diccionario. Por ejemplo, Python tiene que escanear todas las condiciones antes de devolver el valor predeterminado.

emú
fuente
7

puedes usar un dict enviado:

#!/usr/bin/env python


def case1():
    print("This is case 1")

def case2():
    print("This is case 2")

def case3():
    print("This is case 3")


token_dict = {
    "case1" : case1,
    "case2" : case2,
    "case3" : case3,
}


def main():
    cases = ("case1", "case3", "case2", "case1")
    for case in cases:
        token_dict[case]()


if __name__ == '__main__':
    main()

Salida:

This is case 1
This is case 3
This is case 2
This is case 1
Felix Martinez
fuente
6

Simple, no probado; cada condición se evalúa de forma independiente: no hay fallas, pero se evalúan todos los casos (aunque la expresión para activar solo se evalúa una vez), a menos que haya una declaración de interrupción. Por ejemplo,

for case in [expression]:
    if case == 1:
        print(end='Was 1. ')

    if case == 2:
        print(end='Was 2. ')
        break

    if case in (1, 2):
        print(end='Was 1 or 2. ')

    print(end='Was something. ')

imprime Was 1. Was 1 or 2. Was something. (¡Maldita sea! ¿Por qué no puedo tener espacios en blanco finales en bloques de código en línea?) si se expressionevalúa 1, Was 2.si se expressionevalúa 2o Was something.si se expressionevalúa a otra cosa.

Solomon Ucko
fuente
1
Bueno, la caída funciona, pero solo para ir a do_default.
syockit
5

Definiendo:

def switch1(value, options):
  if value in options:
    options[value]()

le permite usar una sintaxis bastante sencilla, con los casos agrupados en un mapa:

def sample1(x):
  local = 'betty'
  switch1(x, {
    'a': lambda: print("hello"),
    'b': lambda: (
      print("goodbye," + local),
      print("!")),
    })

Seguí tratando de redefinir el interruptor de una manera que me permitiera deshacerme del "lambda:", pero me di por vencido. Afinando la definición:

def switch(value, *maps):
  options = {}
  for m in maps:
    options.update(m)
  if value in options:
    options[value]()
  elif None in options:
    options[None]()

Me permitió asignar múltiples casos al mismo código y proporcionar una opción predeterminada:

def sample(x):
  switch(x, {
    _: lambda: print("other") 
    for _ in 'cdef'
    }, {
    'a': lambda: print("hello"),
    'b': lambda: (
      print("goodbye,"),
      print("!")),
    None: lambda: print("I dunno")
    })

Cada caso replicado debe estar en su propio diccionario; switch () consolida los diccionarios antes de buscar el valor. Todavía es más feo de lo que me gustaría, pero tiene la eficiencia básica de usar una búsqueda hash en la expresión, en lugar de un bucle a través de todas las teclas.

William H. Hooper
fuente
5

Creo que la mejor manera es usar las expresiones idiomáticas de Python para mantener su código comprobable . Como se mostró en respuestas anteriores, uso diccionarios para aprovechar las estructuras y el lenguaje de Python. y mantener el código de "caso" aislado en diferentes métodos. A continuación hay una clase, pero puede usar directamente un módulo, globales y funciones. La clase tiene métodos que se pueden probar con aislamiento . Dependiendo de sus necesidades, también puede jugar con métodos y atributos estáticos.

class ChoiceManager:

    def __init__(self):
        self.__choice_table = \
        {
            "CHOICE1" : self.my_func1,
            "CHOICE2" : self.my_func2,
        }

    def my_func1(self, data):
        pass

    def my_func2(self, data):
        pass

    def process(self, case, data):
        return self.__choice_table[case](data)

ChoiceManager().process("CHOICE1", my_data)

Es posible aprovechar este método utilizando también clases como claves de "__choice_table". De esta manera puede evitar el abuso de instancias y mantener todo limpio y comprobable.

Supongamos que tiene que procesar muchos mensajes o paquetes desde la red o su MQ. Cada paquete tiene su propia estructura y su código de gestión (de forma genérica). Con el código anterior, es posible hacer algo como esto:

class PacketManager:

    def __init__(self):
        self.__choice_table = \
        {
            ControlMessage : self.my_func1,
            DiagnosticMessage : self.my_func2,
        }

    def my_func1(self, data):
        # process the control message here
        pass

    def my_func2(self, data):
        # process the diagnostic message here
        pass

    def process(self, pkt):
        return self.__choice_table[pkt.__class__](pkt)

pkt = GetMyPacketFromNet()
PacketManager().process(pkt)


# isolated test or isolated usage example
def test_control_packet():
    p = ControlMessage()
    PacketManager().my_func1(p)

Por lo tanto, la complejidad no se extiende en el flujo del código, sino que se representa en la estructura del código .

J_Zar
fuente
Realmente feo ... la caja del interruptor está muy limpia al leer. No puedo entender por qué no está implementado en Python.
jmcollin92
@AndyClifton: Lo siento ... ¿un ejemplo? Piense cada vez que necesita tener un código de ramificación de decisiones múltiples y puede aplicar este método.
J_Zar
@ jmcollin92: la declaración de cambio es cómoda, estoy de acuerdo. Sin embargo, el programador tiende a escribir declaraciones muy largas y código que no es reutilizable. La forma en que describí es más limpia para probar y más reutilizable, en mi humilde opinión.
J_Zar
@J_Zar: re. Mi solicitud de un ejemplo: sí, lo entiendo, pero estoy luchando para poner esto en el contexto de un código más grande. ¿Podría mostrar cómo podría usar esto en una situación del mundo real?
Andy Clifton
1
@AndyClifton: Lo siento, llego tarde pero publiqué un caso de ejemplo.
J_Zar
5

Ampliando la respuesta de Greg Hewgill : podemos encapsular la solución de diccionario usando un decorador:

def case(callable):
    """switch-case decorator"""
    class case_class(object):
        def __init__(self, *args, **kwargs):
            self.args = args
            self.kwargs = kwargs

        def do_call(self):
            return callable(*self.args, **self.kwargs)

return case_class

def switch(key, cases, default=None):
    """switch-statement"""
    ret = None
    try:
        ret = case[key].do_call()
    except KeyError:
        if default:
            ret = default.do_call()
    finally:
        return ret

Esto se puede usar con el @casedecorador

@case
def case_1(arg1):
    print 'case_1: ', arg1

@case
def case_2(arg1, arg2):
    print 'case_2'
    return arg1, arg2

@case
def default_case(arg1, arg2, arg3):
    print 'default_case: ', arg1, arg2, arg3

ret = switch(somearg, {
    1: case_1('somestring'),
    2: case_2(13, 42)
}, default_case(123, 'astring', 3.14))

print ret

La buena noticia es que esto ya se ha hecho en NeoPySwitch -module. Simplemente instale usando pip:

pip install NeoPySwitch
Tom
fuente
5

Una solución que tiendo a utilizar y que también utiliza diccionarios es:

def decision_time( key, *args, **kwargs):
    def action1()
        """This function is a closure - and has access to all the arguments"""
        pass
    def action2()
        """This function is a closure - and has access to all the arguments"""
        pass
    def action3()
        """This function is a closure - and has access to all the arguments"""
        pass

   return {1:action1, 2:action2, 3:action3}.get(key,default)()

Esto tiene la ventaja de que no intenta evaluar las funciones cada vez, y solo debe asegurarse de que la función externa obtenga toda la información que necesitan las funciones internas.

Tony Suffolk 66
fuente
5

Ha habido muchas respuestas hasta ahora que han dicho: "no tenemos un interruptor en Python, hágalo de esta manera". Sin embargo, me gustaría señalar que la declaración de cambio en sí misma es una construcción de abuso fácil que puede y debe evitarse en la mayoría de los casos porque promueve la programación diferida. Caso en punto:

def ToUpper(lcChar):
    if (lcChar == 'a' or lcChar == 'A'):
        return 'A'
    elif (lcChar == 'b' or lcChar == 'B'):
        return 'B'
    ...
    elif (lcChar == 'z' or lcChar == 'Z'):
        return 'Z'
    else:
        return None        # or something

Ahora, podría hacer esto con una declaración de cambio (si Python ofreció una), pero estaría perdiendo el tiempo porque hay métodos que hacen esto bien. O tal vez, tienes algo menos obvio:

def ConvertToReason(code):
    if (code == 200):
        return 'Okay'
    elif (code == 400):
        return 'Bad Request'
    elif (code == 404):
        return 'Not Found'
    else:
        return None

Sin embargo, este tipo de operación puede y debe manejarse con un diccionario porque será más rápido, menos complejo, menos propenso a errores y más compacto.

Y la gran mayoría de los "casos de uso" para las declaraciones de cambio caerán en uno de estos dos casos; solo hay muy pocas razones para usar uno si ha pensado en su problema a fondo.

Entonces, en lugar de preguntar "¿cómo cambio en Python?", Quizás deberíamos preguntar, "¿por qué quiero cambiar en Python?" porque esa es a menudo la pregunta más interesante y a menudo expondrá fallas en el diseño de lo que sea que esté construyendo.

Ahora, eso no quiere decir que los interruptores nunca deberían usarse tampoco. Las máquinas de estado, los lexers, los analizadores y los autómatas los usan hasta cierto punto y, en general, cuando comienzas desde una entrada simétrica y pasas a una salida asimétrica, pueden ser útiles; solo necesita asegurarse de no usar el interruptor como un martillo porque ve un montón de clavos en su código.

Woody1193
fuente