Quiero escribir una función en Python que devuelva diferentes valores fijos basados en el valor de un índice de entrada.
En otros idiomas, usaría una declaración switch
o case
, pero Python no parece tener una switch
declaración. ¿Cuáles son las soluciones Python recomendadas en este escenario?
python
switch-statement
Michael Schneider
fuente
fuente
switch
en realidad es más "versátil" que algo que devuelve diferentes valores fijos en función del valor de un índice de entrada. Permite ejecutar diferentes piezas de código. En realidad, ni siquiera necesita devolver un valor. Me pregunto si algunas de las respuestas aquí son buenos reemplazos para unaswitch
declaración general , o solo para el caso de devolver valores sin posibilidad de ejecutar partes generales de código.Respuestas:
Podrías usar un diccionario:
fuente
get
método probablemente sería más normal que usar uncollections.defaultdict
en este caso.}.get(x, default)
en su lugar, hazlo si debería haber un valor predeterminado. (Nota: ¡esto es mucho mejor de lo que sucede si deja el valor predeterminado en una declaración de cambio!)Si desea valores predeterminados, puede utilizar el
get(key[, default])
método del diccionario :fuente
Siempre me ha gustado hacerlo de esta manera
De aquí
fuente
.get()
(como las respuestas más altas actuales) deberán evaluar con entusiasmo todas las posibilidades antes de enviarlos, y por lo tanto no solo son (no solo muy, sino) extremadamente ineficientes y tampoco pueden tener efectos secundarios; esta respuesta evita ese problema, pero es más detallada. Simplemente usaría if / elif / else, e incluso esos tardan tanto tiempo en escribirse como 'case'.[value]
, que devolverá solo una de las 3 funciones (suponiendo quevalue
sea 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 conx
como argumento (y el resultado va aresult
). Las otras 2 funciones no serán llamadas.Además de los métodos de diccionario (que realmente me gustan, por cierto), también puede usar
if
-elif
-else
para obtener la funcionalidadswitch
/case
/default
:Esto, por supuesto, no es idéntico al cambio / caso: no puede tener una falla tan fácil como omitir la
break
declaración, pero puede tener una prueba más complicada. Su formato es más agradable que una serie deif
s anidados , aunque funcionalmente es a lo que está más cerca.fuente
get
forma, pero la forma estándar es simplemente más legible.x = the.other.thing
antes. Por lo general, tendría un solo if, múltiples elif y un solo más, ya que es más fácil de entender.if/elif/else
?x in 'bc'
, tenga en cuenta que"" in "bc"
esTrue
.Mi receta favorita de Python para switch / case es:
Corto y simple para escenarios simples.
Compare con más de 11 líneas de código C:
Incluso puede asignar múltiples variables usando tuplas:
fuente
default = -1; result = choices.get(key, default)
.result=key=='a'?1:key==b?2:-1
result = 1 if key == 'a' else (2 if key == 'b' else 'default')
. pero, ¿es legible el revestimiento?Uso:
Pruebas:
fuente
if
declaración estándar ?case(2)
bloque llamó a otra función que usa switch (), entonces al hacercase(2, 3, 5, 7)
etc. para buscar el siguiente caso a ejecutar, usará el valor de cambio establecido por la otra función, no el establecido por la instrucción de cambio actual .Mi favorita es una muy buena receta . Realmente te gustará. Es el más cercano que he visto a las declaraciones de casos de cambio reales, especialmente en las características.
Aquí hay un ejemplo:
fuente
for case in switch()
porwith switch() as case
, tiene más sentido, ya que solo necesita ejecutarse una vez.with
no lo permitebreak
, por lo que la opción de eliminación se elimina.if c in set(range(0,9)): print "digit" elif c in set(map(chr, range(ord('a'), ord('z')))): print "lowercase"
?fuente
value
propiedad (pública) a la clase Switch para que pueda hacer referenciacase.value
a la declaración.Hay un patrón que aprendí del código Twisted Python.
Puede usarlo en cualquier momento que necesite enviar un token y ejecutar un código extendido. En una máquina de estado tendrías
state_
métodos y despachoself.state
. Este modificador puede ampliarse limpiamente heredando de la clase base y definiendo sus propiosdo_
métodos. Muchas veces ni siquiera tendrásdo_
métodos en la clase base.Editar: ¿cómo se usa exactamente eso?
En caso de SMTP, recibirá
HELO
de la transferencia. El código relevante (detwisted/mail/smtp.py
, modificado para nuestro caso) se ve asíRecibirás
' HELO foo.bar.com '
(o podrías recibir'QUIT'
o'RCPT TO: foo'
). Esto se tokeniza enparts
como['HELO', 'foo.bar.com']
. Se toma el nombre de búsqueda del método realparts[0]
.(También se llama al método original
state_COMMAND
, porque usa el mismo patrón para implementar una máquina de estados, es decirgetattr(self, 'state_' + self.mode)
)fuente
Digamos que no solo desea devolver un valor, sino que desea utilizar métodos que cambien algo en un objeto. Usar el enfoque aquí indicado sería:
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:
Entonces obtienes una lista que contiene una función y sus argumentos. De esta manera, solo se devuelve el puntero de función y la lista de argumentos, no se evalúa. 'resultado' luego evalúa la llamada de función devuelta.
fuente
Voy a dejar caer mis dos centavos aquí. La razón por la que no hay una declaración de caso / cambio en Python es porque Python sigue el principio de "Hay una sola forma correcta de hacer algo". Entonces, obviamente, podría encontrar varias formas de recrear la funcionalidad del interruptor / caso, pero la forma pitónica de lograr esto es la construcción if / elif. es decir
Sentí que PEP 8 merecía un guiño aquí. Una de las cosas hermosas de Python es su simplicidad y elegancia. Eso se deriva en gran medida de los principios establecidos en PEP 8, que incluyen "Solo hay una manera correcta de hacer algo"
fuente
ampliando la idea de "dictar como interruptor". si desea usar un valor predeterminado para su conmutador:
fuente
'default'
) de la regla (obtener algo de este dict). Por diseño, los programas Python usan excepciones en un abrir y cerrar de ojos. Dicho esto, el usoget
podría hacer que el código sea un poco más agradable.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.
fuente
Si está buscando una declaración adicional, como "interruptor", construí un módulo de Python que extiende Python. Se llama ESPY como "Estructura mejorada para Python" y está disponible para Python 2.xy Python 3.x.
Por ejemplo, en este caso, el siguiente código podría realizar una declaración de cambio:
que se puede usar así:
así que espy traducirlo en Python como:
fuente
while True:
en la parte superior del código Python generado? Inevitablemente llegarábreak
a la parte inferior del código Python generado, por lo que me parece que tanto elwhile True:
y comobreak
podrían eliminarse. Además, ¿ESPY es lo suficientemente inteligente como para cambiar el nombre decont
si el usuario usa ese mismo nombre en su propio código? En cualquier caso, quiero usar Python vainilla para no usar esto, pero no obstante es genial. +1 por pura frescura.while True:
ybreak
es la de permitir, pero no requerir de paso al siguiente.Encontré que una estructura de interruptor común:
se puede expresar en Python de la siguiente manera:
o formateado de una manera más clara:
En lugar de ser una declaración, la versión de Python es una expresión, que se evalúa como un valor.
fuente
parameter
yp1==parameter
f = lambda x: 'a' if x==0 else 'b' if x==1 else 'c'
. Cuando más tarde llaméf(2)
, recibí'c'
;f(1)
,'b'
; yf(0)
,'a'
. En cuanto a p1 (x), denota un predicado; siempre que regreseTrue
oFalse
, no importa si es una llamada a una función o una expresión, está bien.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
elif
cadena para casos simples ydict
para casos más grandes o más complejos. También sugiere un conjunto devisit_
métodos (un estilo utilizado por muchos marcos de servidores) para algunos casos:Las preguntas frecuentes también mencionan PEP 275 , que fue escrito para obtener una decisión oficial de una vez por todas sobre la adición de declaraciones de cambio de estilo C. Pero esa PEP se aplazó a Python 3, y solo se rechazó oficialmente como una propuesta separada, PEP 3103 . La respuesta fue, por supuesto, no, pero las dos PEP tienen enlaces a información adicional si está interesado en los motivos o el historial.
Una cosa que surgió varias veces (y se puede ver en PEP 275, a pesar de que se recortó como una recomendación real) es que si realmente le molesta tener 8 líneas de código para manejar 4 casos, frente a los 6 líneas que tendrías en C o Bash, siempre puedes escribir esto:
Esto no es exactamente alentado por PEP 8, pero es legible y no demasiado unidiomático.
Durante más de una década desde que se rechazó el PEP 3103, el tema de las declaraciones de casos de estilo C, o incluso la versión un poco más poderosa en Go, se ha considerado muerto; cada vez que alguien lo menciona en python-ideas o -dev, se les remite a la antigua decisión.
Sin embargo, la idea de la coincidencia completa de patrones de estilo ML surge cada pocos años, especialmente desde que lenguajes como Swift y Rust lo han adoptado. El problema es que es difícil aprovechar la coincidencia de patrones sin tipos de datos algebraicos. Si bien Guido ha simpatizado con la idea, a nadie se le ocurrió una propuesta que se adapte muy bien a Python. (Puede leer mi Strawman 2014 para ver un ejemplo.) Esto podría cambiar con
dataclass
3.7 y algunas propuestas esporádicas para unenum
tipo de suma más potente para manejar, o con varias propuestas para diferentes tipos de enlaces de declaración local (como PEP 3150 , o el conjunto de propuestas actualmente en discusión sobre -ideas). Pero hasta ahora, no lo ha hecho.También hay ocasionalmente propuestas para la coincidencia de estilo Perl 6, que es básicamente una mezcla de todo, desde
elif
expresiones regulares hasta cambio de tipo de despacho único.fuente
Solución para ejecutar funciones:
donde foo1 (), foo2 (), foo3 () y default () son funciones
fuente
foo2()
, elfoo1()
,foo3()
ydefault()
las funciones están también van a ejecutar, es decir, las cosas podrían tomar un largo tiempoget(option)()
. problema resuelto.No encontré la respuesta simple que estaba buscando en ninguna parte de la búsqueda de Google. Pero lo descubrí de todos modos. Es realmente bastante simple. Decidí publicarlo, y tal vez evitar algunos rasguños menos en la cabeza de otra persona. La clave es simplemente "in" y tuplas. Aquí está el comportamiento de la instrucción switch con fall-through, incluido el fall-through RANDOM.
Proporciona:
fuente
break
al final del código para acase
. Pero creo que no necesitamos ese "fracaso" :)Las soluciones que uso:
Una combinación de 2 de las soluciones publicadas aquí, que es relativamente fácil de leer y admite valores predeterminados.
dónde
mira
"lambda x: x - 2"
en el dict y lo usa conx=23
no lo encuentra en el dict y usa el valor predeterminado
"lambda x: x - 22"
conx=44
.fuente
fuente
fuente
Me gustó la respuesta de Mark Bies
Como la
x
variable debe usarse dos veces, modifiqué las funciones lambda a sin parámetros.Tengo que correr con
results[value](value)
Editar: Me di cuenta de que puedo usar el
None
tipo con diccionarios. Entonces esto emularíaswitch ; case else
fuente
result[None]()
?result = {'a': 100, None:5000}; result[None]
None:
comporta comodefault:
.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.
fuente
puedes usar un dict enviado:
Salida:
fuente
Simple, no probado; cada condición se evalúa de forma independiente: no hay fallas, pero se evalúan todos los casos (aunque la expresión para activar solo se evalúa una vez), a menos que haya una declaración de interrupción. Por ejemplo,
imprime
Was 1. Was 1 or 2. Was something.
(¡Maldita sea! ¿Por qué no puedo tener espacios en blanco finales en bloques de código en línea?) si seexpression
evalúa1
,Was 2.
si seexpression
evalúa2
oWas something.
si seexpression
evalúa a otra cosa.fuente
Definiendo:
le permite usar una sintaxis bastante sencilla, con los casos agrupados en un mapa:
Seguí tratando de redefinir el interruptor de una manera que me permitiera deshacerme del "lambda:", pero me di por vencido. Afinando la definición:
Me permitió asignar múltiples casos al mismo código y proporcionar una opción predeterminada:
Cada caso replicado debe estar en su propio diccionario; switch () consolida los diccionarios antes de buscar el valor. Todavía es más feo de lo que me gustaría, pero tiene la eficiencia básica de usar una búsqueda hash en la expresión, en lugar de un bucle a través de todas las teclas.
fuente
Creo que la mejor manera es usar las expresiones idiomáticas de Python para mantener su código comprobable . Como se mostró en respuestas anteriores, uso diccionarios para aprovechar las estructuras y el lenguaje de Python. y mantener el código de "caso" aislado en diferentes métodos. A continuación hay una clase, pero puede usar directamente un módulo, globales y funciones. La clase tiene métodos que se pueden probar con aislamiento . Dependiendo de sus necesidades, también puede jugar con métodos y atributos estáticos.
Es posible aprovechar este método utilizando también clases como claves de "__choice_table". De esta manera puede evitar el abuso de instancias y mantener todo limpio y comprobable.
Supongamos que tiene que procesar muchos mensajes o paquetes desde la red o su MQ. Cada paquete tiene su propia estructura y su código de gestión (de forma genérica). Con el código anterior, es posible hacer algo como esto:
Por lo tanto, la complejidad no se extiende en el flujo del código, sino que se representa en la estructura del código .
fuente
Ampliando la respuesta de Greg Hewgill : podemos encapsular la solución de diccionario usando un decorador:
Esto se puede usar con el
@case
decoradorLa buena noticia es que esto ya se ha hecho en NeoPySwitch -module. Simplemente instale usando pip:
fuente
Una solución que tiendo a utilizar y que también utiliza diccionarios es:
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.
fuente
Ha habido muchas respuestas hasta ahora que han dicho: "no tenemos un interruptor en Python, hágalo de esta manera". Sin embargo, me gustaría señalar que la declaración de cambio en sí misma es una construcción de abuso fácil que puede y debe evitarse en la mayoría de los casos porque promueve la programación diferida. Caso en punto:
Ahora, podría hacer esto con una declaración de cambio (si Python ofreció una), pero estaría perdiendo el tiempo porque hay métodos que hacen esto bien. O tal vez, tienes algo menos obvio:
Sin embargo, este tipo de operación puede y debe manejarse con un diccionario porque será más rápido, menos complejo, menos propenso a errores y más compacto.
Y la gran mayoría de los "casos de uso" para las declaraciones de cambio caerán en uno de estos dos casos; solo hay muy pocas razones para usar uno si ha pensado en su problema a fondo.
Entonces, en lugar de preguntar "¿cómo cambio en Python?", Quizás deberíamos preguntar, "¿por qué quiero cambiar en Python?" porque esa es a menudo la pregunta más interesante y a menudo expondrá fallas en el diseño de lo que sea que esté construyendo.
Ahora, eso no quiere decir que los interruptores nunca deberían usarse tampoco. Las máquinas de estado, los lexers, los analizadores y los autómatas los usan hasta cierto punto y, en general, cuando comienzas desde una entrada simétrica y pasas a una salida asimétrica, pueden ser útiles; solo necesita asegurarse de no usar el interruptor como un martillo porque ve un montón de clavos en su código.
fuente