sintaxis - use switch in python
¿Reemplazos para la instrucción switch en Python? (30)
Quiero escribir una función en Python que devuelva diferentes valores fijos según el valor de un índice de entrada.
En otros idiomas, usaría una declaración de switch
o case
, pero Python no parece tener una declaración de switch
. ¿Cuáles son las soluciones Python recomendadas en este escenario?
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; declaración, pero puede tener una prueba más complicada. Su formato es mejor que una serie de ifs anidados, aunque funcionalmente eso es lo que está más cerca.
Creo que la mejor manera es usar los modismos del lenguaje python para mantener su código comprobable . Como se mostró en las respuestas anteriores, utilizo diccionarios para aprovechar las estructuras y el lenguaje de Python y mantener el código del "caso" aislado en diferentes métodos.A continuación hay una clase, pero puedes usar directamente un módulo, funciones globales y funciones. La clase tiene métodos que pueden ser probados 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 la instancia y mantener todo limpio y comprobable.
Suponiendo que tiene que procesar una gran cantidad de 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 propaga en el flujo de código, sino que se representa en la estructura del código .
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.
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.
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á 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)
)
He hecho una solución (relativamente) flexible y reutilizable para esto. Se puede encontrar en GitHub como esta esencia . Si el resultado de la función de cambio es llamable, se llama automáticamente.
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, en comparación con 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 tema 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 lo 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 2014 para ver un ejemplo). Esto podría cambiar con dataclass
en 3.7 y algunas propuestas esporádicas para una enum
más poderosa para manejar tipos de sumas, o con varias propuestas para diferentes tipos de enlaces locales de sentencias (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
hasta la expresión regular y el cambio de tipo de envío único.
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 con x=23
.get(''xxx'', lambda x: x - 22)(44)
no lo encuentra en el dict y usa el predeterminado "lambda x: x - 22"
con x=44
.
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 el switch ; case else
switch ; case else
Mi favorita es una muy buena recipe . Realmente te gustará. Es el más cercano que he visto a las declaraciones de casos de cambio reales, especialmente en características.
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!"
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''))
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.
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])
diccionario get(key[, default])
:
def f(x):
return {
''a'': 1,
''b'': 2
}.get(x, 9) # 9 is default if x not found
Si estás buscando una declaración 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 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
Si tiene un bloque de casos complicado, puede considerar usar una tabla de búsqueda de diccionarios de funciones ...
Si no lo has hecho antes, es una buena idea entrar en tu 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()
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)
Solo 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 solo una forma 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 "Solo hay una manera 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''
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í intentando redefinir el interruptor de una manera que me permitiera deshacerme de "lambda:", pero me rendí. 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 tiene que 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.
Estaba bastante confundido después de leer la respuesta, 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";
}
}
Compruebe la Source para obtener más información sobre la asignación de diccionario a las funciones.
Hice esta solución pequeña y limpia.
result = {
''case1'': foo1,
''case2'': foo2,
''case3'': foo3,
''default'': default,
}.get(option)()
donde foo1 (), foo2 (), foo3 () y default () son funciones
Si no se preocupa por perder el resaltado de sintaxis dentro de las suites de casos, puede hacer lo siguiente:
exec {
1: """
print (''one'')
""",
2: """
print (''two'')
""",
3: """
print (''three'')
""",
}.get(value, """
print (''None'')
""")
Donde value
esta el valor En C, esto sería:
switch (value) {
case 1:
printf("one");
break;
case 2:
printf("two");
break;
case 3:
printf("three");
break;
default:
printf("None");
break;
}
También podemos crear una función de ayuda para hacer esto:
def switch(value, cases, default):
exec cases.get(value, default)
Así que podemos usarlo así para el ejemplo con uno, dos y tres:
switch(value, {
1: """
print (''one'')
""",
2: """
print (''two'')
""",
3: """
print (''three'')
""",
}, """
print (''None'')
""")
Una solución que tiendo a usar y 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 tiene que asegurarse de que la función externa obtenga toda la información que las funciones internas necesitan.
Ampliando la respuesta de Greg Hewgill : podemos encapsular la solución del diccionario utilizando 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 puede ser usado con el @case
-decorador
@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
Inspirado por esta asombrosa respuesta . No requiere código externo. No probado. Caerse a través no funciona correctamente.
for case in [expression]:
if case == 1:
do_stuff()
# Fall through
# Doesn''t fall through INTO the later cases
if case in range(2, 5):
do_other_stuff()
break
do_default()
# 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
class Switch:
def __init__(self, value): self._val = value
def __enter__(self): return self
def __exit__(self, type, value, traceback): return False # Allows traceback to occur
def __call__(self, *mconds): return self._val in mconds
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
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.
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.