switch - while case in python




¿Reemplazos para la instrucción switch en Python? (20)

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.

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?


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 .


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


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 .


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

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

De aquí


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.


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 valueesta 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')
""")

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

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.




switch-statement