¿Reemplazos para la declaración del interruptor en Python?


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 un switch o una declaración de case , pero Python no parece tener una declaración de switch . ¿Cuáles son las soluciones de Python recomendadas en este escenario?



Answers


Puedes usar un diccionario:

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



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

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 del diccionario (que realmente me gustan, BTW), también puede usar if-elif-else para 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 al cambio / caso: no se puede pasar tan fácilmente como dejar el receso; declaración, pero puede tener una prueba más complicada. Su formateo es más agradable que una serie de if anidados, aunque funcionalmente eso es lo que más se acerca.




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.



Mi receta favorita de Python para el interruptor / caja es:

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

Corto 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 mediante el uso de tuplas:

choices = {'a': (1, 2, 3), 'b': (4, 5, 6)}
(result1, result2, result3) = choices.get(key, ('default1', 'default2', 'default3'))



Hay un patrón que aprendí del código de 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'

Puedes utilizarlo en cualquier momento que necesites enviar un token y ejecutar un fragmento de código ampliado. En una máquina de estados, state_ métodos state_ y se self.state en self.state . Este cambio se puede extender limpiamente heredando de la clase base y definiendo sus propios métodos do_ . Muchas veces ni siquiera tendrás do_ métodos en la clase base.

Editar: cómo exactamente es eso usado

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

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




Mi favorita es una receta realmente buena. Realmente te gustará. Es el más cercano que he visto a las declaraciones de cambio reales, especialmente en las 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!"



Digamos que no desea simplemente devolver un valor, pero desea usar métodos que cambien 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))

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)

Entonces obtienes una lista que contiene una función y sus argumentos. De esta forma, solo se devuelven el puntero a la función y la lista de argumentos, no se evalúan. 'resultado' luego evalúa la llamada de función devuelta.




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, cond, *mconds): return self._val in (cond,)+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



expandiendo la idea de "dict como cambio". si quieres usar un valor predeterminado para tu cambio:

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



Si buscas una declaración extra, como "cambiar", construí un módulo de python que amplía Python. Se llama ESPY como "estructura mejorada para Python" y está disponible tanto para Python 2.x como para Python 3.x.

Por ejemplo, en este caso, una declaración de cambio podría ser realizada por 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 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 tradúzcalo 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 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 búsqueda de mayúsculas / minúsculas o llamará a cada una de sus funciones a medida que se crea el bloque de diccionario / caja. 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()



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. 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 declaración de cambio con fall-through, que incluye RANDOM fall-through.

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é una estructura de cambio 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.




Las soluciones que uso:

Una combinación de 2 de las soluciones publicadas aquí, que es relativamente fácil de leer y admite 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 predeterminado "lambda x: x - 22" con x=44 .




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

Solo sentí que PEP 8 merecía un guiño aquí. Una de las cosas bellas 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 forma correcta de hacer algo"




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 invocable, se llama automáticamente.




# 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



Me gustó la respuesta de Mark Bies

Como la variable x debe usarse dos veces, modifiqué las funciones lambda a parameterless.

Tengo que correr con los 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: noté que puedo usar None type with con diccionarios. Entonces esto emularía el switch ; case else switch ; case else




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 tanto en condiciones como en valores devueltos.

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.




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 cambio de una manera que me permitiera deshacerme de la "lambda", pero me rendí. Afinando 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ó mapear múltiples casos al mismo código y suministrar 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 en todas las teclas.




Creo que la mejor manera es utilizar las expresiones idiomáticas de Python para mantener el código comprobable . Como se mostró en las respuestas anteriores, utilizo diccionarios para aprovechar las estructuras y el lenguaje python y mantener el código "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 pueden probarse 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 usando también clases como claves de "__choice_table". De esta forma, puede evitar el abuso de la instancia y mantener todo limpio y comprobable.

Supongamos que tiene que procesar una gran cantidad de mensajes o paquetes de 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 extiende en el flujo de código, sino que se representa en la estructura del código .




Si usted no se preocupe perder resaltado de sintaxis dentro de la caja suites, puede hacer lo siguiente:

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

Donde valuees 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 utilizar de esta manera para el ejemplo con uno, dos y tres:

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



Yo sólo tiene que utilizar si elif declaraciones / / else. Creo que es lo suficientemente bueno como para reemplazar la sentencia switch.




Ampliando la respuesta de Greg Hewgill - Podemos encapsular el diccionario solución 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 entonces se puede utilizar con la @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

La buena noticia es que esto ya se ha hecho en NeoPySwitch -module. Basta con instalar usando pip:

pip install NeoPySwitch



Sólo el mapeo de alguna clave para un cierto código no es realmente y emisión como la mayoría de la gente ha demostrado mediante el dict. El verdadero truco está tratando de emular a toda la caída y romper a través cosa. No creo que he escrito una declaración de caso en el que he usado "característica". Aquí hay un ir a gota a través.

def case(list): reduce(lambda b, f: (b | f[0], {False:(lambda:None),True:f[1]}[b | f[0]]())[0], list, False)

case([
    (False, lambda:print(5)),
    (True, lambda:print(4))
])

Yo estaba realmente imaginando como una sola instrucción. Espero que se me permite el formato tonta.

reduce(
    initializer=False,
    function=(lambda b, f:
        ( b | f[0]
        , { False: (lambda:None)
          , True : f[1]
          }[b | f[0]]()
        )[0]
    ),
    iterable=[
        (False, lambda:print(5)),
        (True, lambda:print(4))
    ]
)

Espero que sea válida en Python. Se debe dar le cae a través. por supuesto, los controles booleanos pueden ser expresiones y si quería que fueran evaluados con pereza que podrían envolver a todos en una lambda. Yo no estaría en el disco para que sea acepta después de la ejecución de algunos de los elementos de la lista tampoco. Sólo hacer la tupla (bool, bool, función), donde el segundo bool indica si ha dañado o caído a través.







Si está realmente sólo devuelve un valor predeterminado, fijo, se puede crear un diccionario con todos los posibles índices de entrada como las claves, junto con sus valores correspondientes. Además, realmente no se puede querer una función para hacer esto - a menos que esté calculando el valor de retorno de alguna manera.

Ah, y si tienes ganas de hacer conexión como algo, ver aquí .




Yo estaba bastante confundido después de leer la respuesta, pero esto se 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 fuente para más información sobre la cartografía diccionario para funciones.