¿Reemplazos para la instrucción switch en Python?

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

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

Podrías usar un diccionario:

 def f(x): return { 'a': 1, 'b': 2, }[x] 

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

 def f(x): return { 'a': 1, 'b': 2 }.get(x, 9) # 9 is default if x not found 

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í

Además de los métodos de diccionario (que realmente me gustan, por cierto), también puede usar if-elif-else para obtener la funcionalidad de cambio / caso / predeterminado:

 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 

Por supuesto, esto no es idéntico al interruptor / caja: no puede tener caídas tan fácilmente como dejar de lado la ruptura; statement, pero puede tener una prueba más complicada. Su formato es mejor que una serie de ifs nesteds, aunque funcionalmente eso es lo que está más cerca.

Mi receta favorita de Python para cambio / caja es:

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

Corto y sencillo para escenarios sencillos.

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 puedes asignar múltiples variables usando tuplas:

 choices = {'a': (1, 2, 3), 'b': (4, 5, 6)} (result1, result2, result3) = choices.get(key, ('default1', 'default2', 'default3')) 
 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. 

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 fragmento de código extendido. En una máquina de estados tendría métodos state_ , y despacho en self.state . Este modificador se puede extender limpiamente heredando de la clase base y definiendo sus propios métodos do_ . Muchas veces ni siquiera tendrá métodos do_ en la clase base.

Edición: cómo se usa exactamente

En caso de SMTP recibirás HELO del cable. El código relevante (de twisted/mail/smtp.py , modificado para nuestro caso) tiene este aspecto

 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á ' HELO foo.bar.com ' (o podría obtener 'QUIT' o 'RCPT TO: foo' ). Esto se tokeniza en parts como ['HELO', 'foo.bar.com'] . El nombre real de búsqueda de método se toma de las parts[0] .

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

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 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!" 
 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 

Digamos que no solo desea devolver un valor, sino que desea utilizar métodos que cambian algo en un objeto. Usando el enfoque indicado aquí 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) 

Así que obtienes una lista que contiene una función y sus argumentos. De esta manera, solo el puntero a la función y la lista de argumentos se devuelven, no se evalúan. ‘resultado’ luego evalúa la función devuelta llamada.

Solo voy a dejar caer mis dos centavos aquí. La razón por la que no hay una statement de caso / cambio en Python es porque Python sigue el principio de “Hay una sola manera correcta de hacer algo”. Obviamente, podría encontrar varias formas de recrear la funcionalidad de cambio / 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" 

Acabo de sentir que PEP 8 merecía un asentimiento aquí. Una de las cosas hermosas de Python es su simplicidad y elegancia. Esto se deriva en gran parte de los principios establecidos en nuestro PEP 8, que incluyen “Sólo hay una forma correcta de hacer algo”

ampliando la idea de “dict 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' 

Si tiene un bloque de casos complicado, puede considerar usar una tabla de búsqueda de diccionarios 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 búsqueda de caja / diccionario o llamará a cada una de sus funciones a medida que se crea el diccionario / bloque de caja. Recuerda esto porque solo quieres 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() 

Si estás buscando una statement extra, 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, una instrucción de cambio podría realizarse mediante el siguiente código:

 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 utilizar de esta manera:

 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 

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 muy 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 switch con caída directa, incluida la caída aleatoria aleatoria.

 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. 

Encontré que una estructura de interruptor común:

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

Se puede express 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 statement, la versión de python es una expresión, que se evalúa como un valor.

Las soluciones que utilizo:

Una combinación de 2 de las soluciones publicadas aquí, que es relativamente fácil de leer y es compatible con los 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 con x=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 .

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

Primero, las preguntas frecuentes oficiales de Python cubren esto, y recomiendan la cadena elif para casos simples y el dict para casos más grandes o más complejos. También sugiere un conjunto de métodos visit_ (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() 

La sección de Preguntas Frecuentes también menciona el 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 ese PEP fue en realidad diferido a Python 3, y solo fue rechazado 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 el historial.


Una cosa que surgió varias veces (y se puede ver en PEP 275, aunque se recortó como una recomendación real) es que si realmente te 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') 

PEP 8 no alienta exactamente esto, pero es legible y no demasiado unidiomático.


Durante más de una década desde que se rechazó el PEP 3103, el problema de las declaraciones de casos de estilo C, o incluso la versión ligeramente más potente en Go, se ha considerado muerta; Cada vez que alguien lo menciona en Python-Ideas o -Dev, se refieren a la decisión anterior.

Sin embargo, la idea de una coincidencia de patrones de estilo ML completa surge cada pocos años, especialmente desde que lenguajes como Swift y Rust la han adoptado. El problema es que es difícil obtener mucho uso de la coincidencia de patrones sin tipos de datos algebraicos. Si bien Guido simpatiza con la idea, a nadie se le ocurre una propuesta que encaje muy bien en Python. (Puede leer mi Strawman de 2014 para ver un ejemplo). Esto podría cambiar con la dataclass de dataclass en 3.7 y algunas propuestas esporádicas para una enum más poderosa para manejar tipos de sum, o con varias propuestas para diferentes tipos de enlaces de declaraciones locales (como PEP 3150) . o el conjunto de propuestas actualmente en discusión en -ideas). Pero hasta ahora, no lo ha hecho.

Ocasionalmente, también hay propuestas para la coincidencia del estilo Perl 6, que es básicamente una mezcla de todo, desde elif a regex hasta el cambio de tipo de envío único.

 # 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 
 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 

Me gustó la respuesta de Mark Bies.

Como la variable x debe usar 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' 

Edición: Noté que puedo usar el tipo None con los diccionarios. Así que esto emularía al switch ; case else switch ; case else

 def f(x): return 1 if x == 'a' else\ 2 if x in 'bcd' else\ 0 #default 

Short and easy to read, has a default value and supports expressions in both conditions and return values.

However, it is less efficient than the solution with a dictionary. For example, Python has to scan through all the conditions before returning the default value.

I think the best way is to use the python language idioms to keep your code testable . As showed in previous answers, I use dictionaries to take advantage of python structures and language and keep the “case” code isolated in different methods. Below there is a class, but you can use directly a module, globals and functions. The class has methods that can be tested with isolation . Dependending to your needs, you can play with static methods and attributes too.

 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) 

It is possible to take advantage of this method using also classes as keys of “__choice_table”. In this way you can avoid isinstance abuse and keep all clean and testable.

Supposing you have to process a lot of messages or packets from the net or your MQ. Every packet has its own structure and its management code (in a generic way). With the above code it is possible to do something like this:

 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) 

So complexity is not spread in the code flow but it is rendered in code structure .

A solution I tend to use which also makes use of dictionaries is :

 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)() 

This has the advantage that it doesn’t try to evaluate the functions every time, and you just have to ensure that the outer function gets all the information that the inner functions need.

you can use a dispatched dict:

 #!/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 

Simple, not tested; each condition is evaluated independently: there is no fall-through, but all cases are evaluated (although the expression to switch on is only evaluated once), unless there is a break statement. 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. ') 

prints Was 1. Was 1 or 2. Was something. (Dammit! Why can’t I have trailing whitespace in inline code blocks?) if expression evaluates to 1 , Was 2. if expression evaluates to 2 , or Was something. if expression evaluates to something else.

Solution to run functions:

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

where foo1(), foo2(), foo3() and default() are functions

Defining:

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

allows you to use a fairly straightforward syntax, with the cases bundled into a map:

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

I kept trying to redefine switch in a way that would let me get rid of the “lambda:”, but gave up. Tweaking the definition:

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

Allowed me to map multiple cases to the same code, and to supply a default option:

 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") }) 

Each replicated case has to be in its own dictionary; switch() consolidates the dictionaries before looking up the value. It’s still uglier than I’d like, but it has the basic efficiency of using a hashed lookup on the expression, rather than a loop through all the keys.

If you don’t worry losing syntax highlight inside the case suites, you can do the following:

 exec { 1: """ print ('one') """, 2: """ print ('two') """, 3: """ print ('three') """, }.get(value, """ print ('None') """) 

Where value is the value. In C, this would be:

 switch (value) { case 1: printf("one"); break; case 2: printf("two"); break; case 3: printf("three"); break; default: printf("None"); break; } 

We can also create a helper function to do this:

 def switch(value, cases, default): exec cases.get(value, default) 

So we can use it like this for the example with one, two and three:

 switch(value, { 1: """ print ('one') """, 2: """ print ('two') """, 3: """ print ('three') """, }, """ print ('None') """) 

Expanding on Greg Hewgill’s answer – We can encapsulate the dictionary-solution using a decorator:

 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 

This can then be used with the @case -decorator

 @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 

The good news are that this has already been done in NeoPySwitch -module. Simply install using pip:

 pip install NeoPySwitch