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

1717

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

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

7
  • 77
    PEP relacionado, escrito por el propio Guido: PEP 3103
    chb
    16/06/12 a las 17:22
  • 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. 9 de ene. De 2014 a las 13:46
  • 15
    En todas las soluciones aquí falta la detección de valores de casos duplicados . Como principio a prueba de fallas, esta puede ser una pérdida más importante que el rendimiento o la característica de falla. 17/10/2014 a las 19:04
  • 6
    switches en realidad más "versátil" que algo que devuelve diferentes valores fijos basados ​​en el 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 piezas generales de código. 14 de mayo de 2017 a las 21:29
  • 3
    De la misma manera, la sintaxis como el caso de Ruby ... when ... (o la coincidencia de Scala, el caso de Haskell, el dado de Perl / cuándo) cumplen con un caso de uso común y ofrecen una poderosa abstracción. si ... elif ... es un mal sustituto. 1 oct 2017 a las 7:14
1786

La respuesta original a continuación se escribió en 2008. Desde entonces, Python 3.10 (2021) introdujo la declaración match-case que proporciona una implementación de primera clase de un "interruptor" para Python. Por ejemplo:

def f(x):
    match x:
        case 'a':
            return 1
        case 'b':
            return 2
        case _:        
            return 0   # 0 is the default case if x is not found

La declaración match- casees considerablemente más poderosa que este simple ejemplo.


Podrías usar un diccionario:

def f(x):
    return {
        'a': 1,
        'b': 2,
    }[x]
24
  • 114
    ¿Qué sucede si no se encuentra x?
    Nick
    19 de septiembre de 2008 a las 15:46
  • 58
    @nick: puedes usar defaultdict 19 de septiembre de 2008 a las 17:38
  • 428
    Recomendaría poner el dict fuera de la función si el rendimiento es un problema, por lo que no reconstruye el dict en cada llamada de función
    Claudiu
    23/108 a las 16:22
  • sesenta y cinco
    @EliBendersky, Usar el getmétodo probablemente sería más normal que usar a collections.defaultdicten este caso. 23/02/12 a las 16:38
  • 36
    @Nick, se lanza una excepción; hágalo }.get(x, default)en su lugar si debería haber un valor predeterminado. (Nota: ¡esto es mucho mejor que lo que sucede si dejas el valor predeterminado desactivado en una declaración de cambio!) 23/02/12 a las 16:39
1501

Si le gustan los valores predeterminados, puede usar el get(key[, default])método del diccionario :

def f(x):
    return {
        'a': 1,
        'b': 2
    }.get(x, 9)    # 9 is default if x is not found
5
  • 13
    ¿Qué pasa si 'a' y 'b' coinciden con 1, y 'c' y 'd' coinciden con 2? 9/04/10 a las 7:57
  • 14
    @JM: Bueno, obviamente, las búsquedas en el diccionario no admiten fallos. Puede hacer una búsqueda doble en el diccionario. Es decir, 'a' y 'b' apuntan a answer1 y 'c' y 'd' apuntan a answer2, que están contenidos en un segundo diccionario.
    Nick
    9/04/10 a las 9:54
  • 3
    esto es mejor pasar un valor predeterminado 13/10/2016 a las 11:01
  • 1
    Hay un problema con este enfoque, primero cada vez que llames a f vas a crear el dict nuevamente, segundo, si tienes un valor más complejo, puedes obtener una excepción, 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 generará IndexError 30 de agosto de 2017 a las 8:01
  • 2
    @Idan: La cuestión era replicar el cambio. Estoy seguro de que también podría romper este código si intentara poner valores extraños. Sí, se volverá a crear, pero es fácil de arreglar.
    Nick
    19 de septiembre de 2017 a las 21:59
452

Siempre me ha gustado hacerlo así

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

De aquí

15
  • 5
    Está pidiendo valores fijos. ¿Por qué generar una función para calcular algo cuando se trata de una búsqueda? Sin embargo, una solución interesante para otros problemas.
    Nick
    21/01/10 a las 17:06
  • 33
    tal vez no sea una buena idea usar lambda en este caso porque en realidad se llama a lambda cada vez que se construye el diccionario.
    Asher
    22/04/12 a las 21:48
  • 15
    Lamentablemente, esto es lo más cercano a la gente. Los métodos que utilizan .get()(como las respuestas más altas actuales) necesitará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. Solo usaría if / elif / else, e incluso esos tardan tanto en escribir como 'case'. 17 de marzo de 2014 a las 13:48
  • 14
    ¿No evaluaría esto todas las funciones / lambdas cada vez en todos los casos, incluso si solo está devolviendo uno de los resultados?
    slf
    6 de agosto de 2014 a las 19:04
  • 24
    @slf No, cuando el flujo de control alcanza ese fragmento de código, construirá 3 funciones (mediante el uso de las 3 lambdas) y luego construirá un diccionario con esas 3 funciones como valores, pero permanecen sin llamar (la evaluación es ligeramente ambigua 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 un xargumento (y el resultado va a result). Las otras 2 funciones no se llamarán. 18 de septiembre de 2015 a las 6:11
418

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 a switch / case: no puede fallar tan fácilmente como omitir la breakdeclaración, pero puede tener una prueba más complicada. Su formato es más agradable que una serie de mensajes de correo ifelectrónico anidados , aunque funcionalmente eso es a lo que está más cerca.

15
  • 72
    Realmente preferiría esto, usa una construcción de lenguaje estándar y no arroja un KeyError si no se encuentra un caso coincidente 18 de mayo '13 a las 22:30 h.
  • 8
    Pensé en el diccionario / getforma, pero la forma estándar es simplemente más legible. 25 de junio de 2015 a las 6:33
  • 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 deben producirse las coincidencias. En cuanto a x repetido: solo haz un x = the.other.thingantes. Por lo general, tendrías un solo if, múltiples elif y un solo else, ya que es más fácil de entender. 3 de marzo de 2016 a las 6:55
  • 8
    Bien, el "Fall-through al no usar elif" es un poco confuso, sin embargo. ¿Qué pasa con esto: olvidarse de "fallar" y simplemente aceptarlo como dos if/elif/else? 30 de mayo de 2016 a las 11:40
  • 9
    También vale la pena mencionar, cuando use cosas como x in 'bc', tenga en cuenta que "" in "bc"es True. 28/08/18 a las 11:46
276

Python> = 3.10 (versión preliminar)

¡Guau, Python ahora está obteniendo una verdadera sintaxis match/case !

PEP 634 , "coincidencia de patrones estructurales"; aka switch/ case, fue aprobado y agregado a Python 3.10.

Uso de muestra :

match something:
    case 0 | 1 | 2:
        # Matches 0, 1 or 2
        print("Small number")
    case [] | [_]:
        # Matches an empty or single value sequence
        # Matches lists and tuples but not sets
        print("A short sequence")
    case str() | bytes():
        # Something of `str` or `bytes` type
        print("Something string-like")
    case _:
        # Anything not matched by the above
        print("Something else")

Python <= 3.9

Mi receta favorita de Python para switch / case fue:

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

Breve 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'))
13
  • 26
    Encuentro que esta es una respuesta más sólida que la aceptada.
    cerd
    18 de agosto de 2015 a las 23:16
  • 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 dicho uso.
    ChaimG
    1 de marzo de 2016 a las 0:17
  • 3
    @algunos usuarios: Personalmente, encuentro {} .get (,) legible. Para mayor legibilidad para los principiantes de Python, es posible que desee utilizar default = -1; result = choices.get(key, default).
    ChaimG
    1 de marzo de 2016 a las 0:19
  • 9
    comparar con 1 línea de c ++ result=key=='a'?1:key==b?2:-1
    Jasen
    23/08/16 a las 22:01
  • 4
    @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 delineador?
    ChaimG
    24 de agosto de 2016 a las 2:57
111
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.
12
  • 70
    Esto no es seguro contra amenazas. Si se pulsan varios interruptores al mismo tiempo, todos los interruptores toman el valor del último interruptor. 26 de junio de 2013 a las 16:35
  • 56
    Si bien @francescortiz probablemente significa seguro para subprocesos, tampoco es seguro para amenazas. ¡Amenaza los valores de las variables! 16/06/15 a las 16:29
  • 7
    Es probable que el problema de seguridad de subprocesos se solucione mediante el uso de almacenamiento local de subprocesos . O podría evitarse por completo devolviendo una instancia y utilizando esa instancia para las comparaciones de casos. 18 de septiembre de 2015 a las 6:24
  • 6
    @blubberdiblub ¿Pero entonces no es más eficiente usar una ifdeclaración estándar ? 23 de mayo de 2016 a las 17:32
  • 10
    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 para ejecutar, usará el valor de switch establecido por la otra función, no el establecido por la declaración de switch actual. . 17 de agosto de 2017 a las 8:58
65

Mi favorita es una receta realmente buena . 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

He aquí 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!"

Algunos de los comentarios indicaron que una solución de administrador de contexto que usa en with foo as caselugar de for case in foopodría ser más limpia, y para declaraciones de cambio grandes, el comportamiento lineal en lugar de cuadrático podría ser un buen toque. Parte del valor en esta respuesta con un bucle for es la capacidad de tener interrupciones y fallos, y si estamos dispuestos a jugar un poco con nuestra elección de palabras clave, también podemos obtener eso en un administrador de contexto:

class Switch:
    def __init__(self, value):
        self.value = value
        self._entered = False
        self._broken = False
        self._prev = None

    def __enter__(self):
        return self

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

    def __call__(self, *values):
        if self._broken:
            return False
        
        if not self._entered:
            if values and self.value not in values:
                return False
            self._entered, self._prev = True, values
            return True
        
        if self._prev is None:
            self._prev = values
            return True
        
        if self._prev != values:
            self._broken = True
            return False
        
        if self._prev == values:
            self._prev = None
            return False
    
    @property
    def default(self):
        return self()

He aquí un ejemplo:

# Prints 'bar' then 'baz'.
with Switch(2) as case:
    while case(0):
        print('foo')
    while case(1, 2, 3):
        print('bar')
    while case(4, 5):
        print('baz')
        break
    while case.default:
        print('default')
        break
5
  • 3
    Lo sustituiría for case in switch()por with switch() as case, tiene más sentido, ya que solo necesita que se ejecute una vez.
    Ski
    12/1213 a las 16:24
  • 5
    @Skirmantas: tenga en cuenta que withno lo permite break, por lo que se elimina la opción fallthrough. 8 de mayo de 2014 a las 16:53
  • 5
    Disculpas por no esforzarme más para determinar esto yo mismo: una respuesta similar anterior no es segura para subprocesos. ¿Es esto? 12 de septiembre de 2014 a las 15:47
  • 1
    @DavidWiniecki Los componentes del código que faltan en los anteriores (y posiblemente los derechos de autor de activestate) parecen ser seguros para subprocesos.
    Jasen
    23/08/16 a las 22:09
  • otra versión de esto sería 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
    31/10/2016 a las 19:08
63
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
4
  • 13
    El uso de administradores de contexto es una buena solución creativa. Recomendaría agregar un poco de explicación y tal vez un enlace a información sobre administradores de contexto para darle a esta publicación algo de contexto;)
    Will
    3 de mayo de 2015 a las 9:13
  • 2
    No me gustan mucho las cadenas if / elif, 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. 2 oct 2017 a las 8:05
  • 2
    Esto es realmente bueno. Una mejora sugerida es agregar una valuepropiedad (pública) a la clase Switch para que pueda hacer referencia a la case.valuedentro de la declaración.
    Peter
    24/01/19 a las 11:03
  • Esta respuesta proporciona la mayor cantidad de funcionalidad similar a la de un interruptor, aunque es bastante simple. El problema con el uso de a dictes que solo puede recuperar datos y no puede ejecutar funciones / métodos.
    moshevi
    5 oct.20 a las 15:55
50

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 en un token y ejecutar un fragmento de código extendido. En una máquina de estado tendrías state_métodos y despacharías self.state. Este modificador se puede extender limpiamente heredando de la clase base y definiendo sus propios do_métodos. Muchas veces ni siquiera tendrá do_métodos en la clase base.

Editar: ¿cómo se usa exactamente?

En caso de SMTP, recibirá HELOdel cable. 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 tokenized en partstan ['HELO', 'foo.bar.com']. El nombre de búsqueda del método real se toma de parts[0].

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

7
  • 4
    No veo el beneficio de este patrón en lugar de simplemente llamar a los métodos directamente: SMTP (). Do_HELO ('foo.bar.com') De acuerdo, puede haber un código común en el método de búsqueda, pero dado que eso también puede ser sobrescrito por la subclase No veo lo que gana con la indirección. 13 de septiembre de 2008 a las 11:35
  • 1
    No sabría qué método llamar de antemano, es decir, 'HELO' proviene de una variable. he agregado un ejemplo de uso a la publicación original
    user6205
    13 de septiembre de 2008 a las 17:45
  • Puedo sugerir simplemente: eval ('SMTP (). Do_' + comando) ('foo.bar.com') 21/06/11 a las 17:32
  • 8
    eval? ¿seriamente? y en lugar de instanciar un método por llamada, podemos instanciarlo una vez y usarlo en todas las llamadas siempre que no tenga un estado interno.
    Mahesh
    19 de marzo de 2013 a las 18:10
  • 1
    En mi opinión, la clave real aquí es el envío usando 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 a las funciones con el prefijo. El propio SMTP llama 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 sola cadena y SMTP los analiza, eso tiene más sentido. Por último, SMTP probablemente tiene otro estado compartido que justifica que sea una clase. 15/08/2013 a las 22:17
29

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

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

Aquí Python evalúa todos los métodos en el diccionario.

Entonces, incluso si su valor es 'a', el objeto aumentará 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 devuelven el puntero de función y la lista de argumentos, no se evalúan. 'resultado' luego evalúa la llamada a la función devuelta.

28

Voy a dejar caer mis dos centavos aquí. La razón por la que no hay una instrucción case / switch en Python es porque Python sigue el principio de "solo hay una forma correcta de hacer algo". Entonces, obviamente, podría encontrar varias formas de recrear la funcionalidad de interruptor / caso, pero la forma Pythonic 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"

Simplemente sentí que PEP 8 merecía un asentimiento 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 "Sólo hay una forma correcta de hacer algo".

8
  • 9
    Entonces, ¿por qué Python tiene bucles for y bucles while? Todo lo que puede hacer con un bucle for lo puede implementar con un bucle while. 1/10/2017 a las 10:01
  • 2
    Verdadero. Los programadores principiantes abusan con demasiada frecuencia de Switch / case. Lo que realmente quieren es el patrón de estrategia . 6/10/2017 a las 12:34
  • Parece que Python desearía que fuera Clojure 16 de febrero de 2018 a las 5:17
  • 1
    @TWRCole No lo creo, Python lo estaba haciendo primero. Python existe desde 1990 y Clojure desde 2007.
    Taylor
    9 de mayo de 2018 a las 16:52
  • 2
    Sin soporte fallido 15 de agosto de 2019 a las 1:33
25

Solución para ejecutar funciones:

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

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

Ejemplo 1 (con parámetros):

option = number['type']
result = {
    'number':     value_of_int,  # result = value_of_int(number['value'])
    'text':       value_of_text, # result = value_of_text(number['value'])
    'binary':     value_of_bin,  # result = value_of_bin(number['value'])
}.get(option)(value['value'])

Ejemplo 2 (sin parámetros):

option = number['type']
result = {
    'number':     func_for_number, # result = func_for_number()
    'text':       func_for_text,   # result = func_for_text()
    'binary':     func_for_bin,    # result = func_for_bin()
}.get(option)()
6
  • 2
    Sí, por ejemplo, si su opción de variable == "case2" su resultado = foo2 () 20/02/18 a las 21:27
  • y así sucesivamente. 20/02/18 a las 21:41
  • Sí, entiendo el propósito. Pero mi preocupación es que si solo deseas foo2(), las foo1(), foo3()y default()las funciones están también van a ejecutar, es decir, las cosas podrían tomar un tiempo largo 21/02/18 a las 17:57
  • 2
    omita el () dentro del diccionario. utilizar get(option)(). problema resuelto.
    timgeb
    10 de abril de 2018 a las 6:00
  • 1
    Excelente, el uso de () es una solución de rejilla, hice una esencia para probarlo gist.github.com/aquintanar/01e9920d8341c5c6252d507669758fe5 17/04/18 a las 5:13
19

Si está buscando extra-statement, como "switch", 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 instrucció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

Eso 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 traduce 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
3
  • Muy bueno, pero ¿cuál es el punto while True:en la parte superior del código Python generado? Inevitablemente llegará breakal final del código de Python generado, por lo que me parece que tanto el while True:como el 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 Vanilla Python, así que no usaré esto, pero es genial de todos modos. +1 por pura frialdad. 29 de junio de 2014 a las 12:56
  • @ArtOfWarfare La razón de la while True:y breaks es permitir pero no requerir fallos . 23/03/19 a las 14:10
  • ¿Este módulo todavía está disponible? 24/03/19 a las 0:57
19

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()
3
  • Me gusta tu solución. Pero, ¿qué pasa si solo necesito pasar algunas variables u objetos? 19/12/18 a las 20:27
  • Esto no funcionará si el método espera parámetros. 29 de enero de 2020 a las 6:35
  • Este es el método que se recomienda oficialmente en las preguntas frecuentes de Python. 18/10/20 a las 19:43
18

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 servidor) 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 se escribió para obtener una decisión oficial de una vez por todas sobre la adición de declaraciones de cambio de estilo C. Pero ese PEP en realidad se aplazó a Python 3, y solo se rechazó oficialmente como una propuesta separada, PEP 3103 . La respuesta fue, por supuesto, no, pero los dos PEP tienen enlaces a información adicional si está interesado en las razones o la historia.


Una cosa que surgió varias veces (y se puede ver en PEP 275, aunque se eliminó como una recomendación real) es que si realmente le molesta tener 8 líneas de código para manejar 4 casos, frente a las 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 recomendado por PEP 8, pero es legible y no demasiado unidiomático.


Durante más de una década desde que se rechazó la PEP 3103, la cuestión de las declaraciones de casos de estilo C, o incluso la versión un poco más poderosa de Go, se ha considerado muerta; cada vez que alguien lo menciona sobre python-ideas o -dev, se refiere a la vieja decisión.

Sin embargo, la idea de la coincidencia de patrones de estilo ML completo surge cada pocos años, especialmente desde que lenguajes como Swift y Rust la han adoptado. El problema es que es difícil sacar mucho provecho de la coincidencia de patrones sin tipos de datos algebraicos. Si bien Guido ha simpatizado con la idea, a nadie se le ha ocurrido una propuesta que se ajuste muy bien a Python. (Puede leer mi hombre de paja de 2014 para ver un ejemplo). Esto podría cambiar con dataclass3.7 y algunas propuestas esporádicas para un enummanejo de tipos de suma más poderoso , o con varias propuestas para diferentes tipos de enlaces locales de declaración (como PEP 3150 , o el conjunto de propuestas que se están debatiendo actualmente sobre ideas). Pero hasta ahora no lo ha hecho.

Ocasionalmente, también hay propuestas para la combinación de estilos de Perl 6, que es básicamente una mezcolanza de todo, desde elifexpresiones regulares hasta cambios de tipo de despacho único.

1
  • Se actualizó la respuesta aceptada. 20 de mayo a las 21:30 h.
18

Ampliando la idea de "dictar como interruptor". Si desea utilizar un valor predeterminado para su conmutador:

def f(x):
    try:
        return {
            'a': 1,
            'b': 2,
        }[x]
    except KeyError:
        return 'default'
2
  • 19
    Creo que es más claro usar .get () en el diccionario con el valor predeterminado especificado. Prefiero dejar Excepciones para circunstancias excepcionales, y corta tres líneas de código y un nivel de sangría sin ser oscuro. 5 de junio de 2009 a las 15:14
  • 10
    Ésta es una circunstancia excepcional. Puede ser o no una circunstancia poco común dependiendo de la utilidad, pero definitivamente es una excepción (recurrir a 'default') de la regla (obtener algo de este dict). Por diseño, los programas de Python utilizan excepciones en un abrir y cerrar de ojos. Dicho esto, el uso getpodría potencialmente hacer que el código sea un poco más agradable. 26/03/10 a las 16:49
16

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.

4
  • También en lugar de ... parámetro ... y p1 (x) ¿qué tal parameteryp1==parameter 11/03/15 a las 15:28
  • @ 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 llamé más tarde f(2), obtuve 'c'; f(1), 'b'; y f(0), 'a'. En cuanto a p1 (x), denota un predicado; siempre que devuelva Trueo False, no importa si es una llamada a función o una expresión, está bien.
    leo
    13/03/15 a las 16:16
  • @ BobStein-VisiBone ¡Sí, tienes razón! Gracias :) Para que la expresión de varias líneas funcione, se deben colocar paréntesis, ya sea como en su sugerencia o como en mi ejemplo modificado.
    leo
    14 de marzo de 2015 a las 5:16
  • Excelente. Ahora borraré todos mis comentarios sobre los parens. 14/03/15 a las 20:48
15

Las soluciones que utilizo:

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)

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

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

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

0
13

No encontré la respuesta simple que estaba buscando en ninguna parte de la búsqueda de Google. Pero lo descubrí de todos modos. Realmente es bastante simple. Decidió publicarlo y tal vez evitar algunos rasguños menos en la cabeza de otra persona. La clave es simplemente "en" y tuplas. Aquí está el comportamiento de la instrucción de cambio con fallos, incluido el fallo 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.
4
  • ¿Dónde está exactamente la caída aquí? 8 de mayo de 2014 a las 16:56
  • ¡UPS! 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 FUNCTIONALITY, entonces desea CAPTURAR ciertas condiciones en todo en una declaración de caso en un switch (un catch all), hasta que llegue a una declaración de interrupción en un switch. 30 de julio de 2014 a las 4:58
  • 2
    En este caso, los valores "Perro" y "Gato" FALL THROUGH y son manejados por la MISMA funcionalidad, que es que se definen como "cuatro patas". Es un ABSTRACTO equivalente a fallar y diferentes valores manejados por la MISMA declaración de caso donde ocurre una ruptura. 30 de julio de 2014 a las 5:16
  • @JDGraham Creo que Jonas se refería a otro aspecto de fallthrough, que ocurre cuando el programador ocasionalmente se olvida de escribir breakal final del código para un case. Pero creo que no necesitamos tal "caída" :) 12 de agosto de 2015 a las 9:00
11
# 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
0
11
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
2
  • Considere incluir una breve descripción de su código y cómo resuelve la pregunta publicada 28 de septiembre de 2018 a las 19:28
  • Bien, agregué un comentario para eso ahora. 30 oct 2018 a las 17:22
11

Puede utilizar un dictado 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()

Producción:

This is case 1
This is case 3
This is case 2
This is case 1
1
  • Algunas veces uso esto pero no es tan claro como si / elif / elif / else
    F.Tamy
    12 de enero a las 8:53
9

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 con diccionarios. Entonces esto emularíaswitch ; case else

3
  • ¿No emula el caso None simplemente result[None]()? 11/03/15 a las 15:19
  • Sí exactamente. quiero decirresult = {'a': 100, None:5000}; result[None] 11/03/15 a las 15:52
  • 4
    Solo comprobar que nadie está pensando se None:comporta como default:. 11/03/15 a las 16:54
8
def f(x):
     return 1 if x == 'a' else\
            2 if x in 'bcd' else\
            0 #default

Breve y fácil de leer, tiene un valor predeterminado y admite expresiones tanto en condiciones como en valores de retorno.

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

8

Simple, no probado; cada condición se evalúa de forma independiente: no hay fallos, pero se evalúan todos los casos (aunque la expresión para activar solo se evalúa una vez), a menos que haya una sentencia 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 al final de los bloques de código en línea?) si se expressionevalúa como 1, Was 2.si se expressionevalúa como 2, o Was something.si se expressionevalúa como otra cosa.

1
  • 1
    Bueno, la caída funciona, pero solo para ir a do_default.
    syockit
    28/01/19 a las 11:17
7

Ha habido muchas respuestas hasta ahora que han dicho, "no tenemos un interruptor en Python, hazlo de esta manera". Sin embargo, me gustaría señalar que la declaración de cambio en sí misma es una construcción de fácil abuso que puede y debe evitarse en la mayoría de los casos porque promueve la programación perezosa. 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 ofreciera una) pero estaría perdiendo el tiempo porque hay métodos que lo hacen 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" de las declaraciones de cambio caerán en uno de estos dos casos; hay muy pocas razones para usar uno si ha pensado detenidamente en su problema.

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 tampoco deban usarse nunca. Las máquinas de estado, los lexers, los analizadores sintácticos y los autómatas los utilizan hasta cierto punto y, en general, cuando se parte de una entrada simétrica y se pasa a una salida asimétrica, pueden resultar útiles; solo necesita asegurarse de no usar el interruptor como un martillo porque ve un montón de clavos en su código.

7

Una solución que suelo usar que también hace uso de 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.

7

Estaba bastante confundido después de leer la respuesta aceptada, pero esto lo aclaró todo:

def numbers_to_strings(argument):
    switcher = {
        0: "zero",
        1: "one",
        2: "two",
    }
    return switcher.get(argument, "nothing")

Este código es análogo a:

function(argument){
    switch(argument) {
        case 0:
            return "zero";
        case 1:
            return "one";
        case 2:
            return "two";
        default:
            return "nothing";
    }
}

Consulte la Fuente para obtener más información sobre la asignación de diccionario a funciones.

2
  • ¿Leyendo qué respuesta? Hay mas de uno. 20 de mayo a las 21:09
  • @PeterMortensen - La respuesta aceptada ... lo arregló.
    Yster
    21 de mayo a las 1:20
6

Definiendo:

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

le permite utilizar 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 de la "lambda:", pero me di por vencido. Ajustando 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 varios 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 claves.

6

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 luego 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 el módulo NeoPySwitch . Simplemente instale usando pip:

pip install NeoPySwitch