Substituições para a declaração switch em Python?
quero escrever uma função em Python que devolve diferentes valores fixos com base no valor de um índice de entrada.
noutras línguas, eu usaria uma declaração switch
ou case
, mas o Python não parece ter uma declaração switch
. Quais são as soluções Python recomendadas neste cenário?
30 answers
Podias usar um dicionário:
def f(x):
return {
'a': 1,
'b': 2,
}[x]
Se quiser predefinições, pode usar o dicionário get(key[, default])
Método:
def f(x):
return {
'a': 1,
'b': 2
}.get(x, 9) # 9 is default if x not found
result = {
'a': lambda x: x * 5,
'b': lambda x: x + 7,
'c': lambda x: x - 2
}[value](x)
Além dos métodos do dicionário (que eu realmente gosto, BTW), você também pode usar if-elif-else para obter a funcionalidade 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
Claro que isto não é idêntico ao switch/case - Você não pode ter queda-através tão facilmente como deixar de fora da declaração;, mas você pode ter um teste mais complicado. Sua formatação é melhor do que uma série de ifs aninhados, mesmo funcionalmente que é o que está mais perto.
A minha receita favorita em Python para switch/case é:
choices = {'a': 1, 'b': 2}
result = choices.get(key, 'default')
Curto e simples para cenários simples.
Comparar com 11 + linhas 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;
}
Pode até atribuir várias variáveis 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))
Utilização:
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
Testes:
n = 2
#Result:
#n is an even number.
#n is a prime number.
n = 11
#Result:
#Only single-digit numbers are allowed.
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'
Pode usá-lo sempre que precisar de enviar num token e executar um código alargado. Em uma máquina estatal você teria Métodos state_
, e expedição em self.state
. Este interruptor pode ser claramente estendido, herdando da classe base e definindo os seus próprios métodos do_
. Muitas vezes você não terá métodos do_
na classe base.
editar: como exatamente é usado
Em caso de SMTP você receberá HELO
do fio. O código relevante (de twisted/mail/smtp.py
, modificado para o nosso caso) parece-se com este
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
Vais receber ' HELO foo.bar.com '
(ou podes receber 'QUIT'
ou 'RCPT TO: foo'
). Isto é tokenized em parts
as ['HELO', 'foo.bar.com']
. O nome verdadeiro da pesquisa do método é retirado de parts[0]
.
(o método original também é chamado state_COMMAND
, porque usa o mesmo padrão para implementar uma máquina de Estado, i.e. getattr(self, 'state_' + self.mode)
)
# 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._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
result = {
'a': obj.increment(x),
'b': obj.decrement(x)
}.get(value, obj.default(x))
O que acontece aqui é que o python avalia todos os métodos no dicionário. Então, mesmo que o seu valor seja 'a', o objeto será aumentado e decrementado por X.
Solução:
func, args = {
'a' : (obj.increment, (x,)),
'b' : (obj.decrement, (x,)),
}.get(value, (obj.default, (x,)))
result = func(*args)
Então você obtém uma lista contendo uma função e seus argumentos. Desta forma, apenas o ponteiro da função e o argumento a lista é devolvida, Não avaliada. o 'resultado' então avalia a chamada de função retornada.
if something:
return "first thing"
elif somethingelse:
return "second thing"
elif yetanotherthing:
return "third thing"
else:
return "default thing"
Achei que o PEP 8 merecia um aceno aqui. Uma das coisas bonitas sobre Python é a sua simplicidade e elegância. Isso é em grande parte derivado dos princípios estabelecidos no PEP 8, incluindo"só há uma maneira certa de fazer algo"
def f(x):
try:
return {
'a': 1,
'b': 2,
}[x]
except KeyError:
return 'default'
Se tem um bloco de casos complicado, pode considerar a utilização de uma tabela de pesquisa do dicionário de funções...
Se ainda não o fez antes é uma boa ideia entrar no seu depurador e ver exactamente como o dicionário procura cada função.
Nota: do not use "() " inside the case/dictionary lookup or it will call each of your functions as the dictionary / case block is created. Lembre-se disso porque você só quer chamar cada função uma vez usando um hash Procura estilo.
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()
Se está à procura de uma declaração extra, como" switch", construí um módulo python que estende o Python. É chamado ESPY como" estrutura melhorada para Python " e está disponível para ambos Python 2.x e Python 3.x.
Por exemplo, neste caso, uma declaração de comutação pode ser executada pelo seguinte 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 pode ser usado assim:
a=3
switch(a):
case(0):
print("Zero")
case(1):
print("Smaller than 2"):
break
else:
print ("greater than 1")
Então espy traduz em 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
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)
Prevê:
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.
Descobri que uma estrutura comum de comutação:
switch ...parameter...
case p1: v1; break;
case p2: v2; break;
default: v3;
Pode ser expresso em Python do seguinte modo:
(lambda x: v1 if p1(x) else v2 if p2(x) else v3)
Ou formatado de uma forma mais clara:
(lambda x:
v1 if p1(x) else
v2 if p2(x) else
v3)
Em vez de ser uma declaração, a versão em python é uma expressão, que avalia para um valor.
As soluções que uso:
Uma combinação de 2 das soluções postadas aqui, que é relativamente fácil de ler e suporta defaults.
result = {
'a': lambda x: x * 5,
'b': lambda x: x + 7,
'c': lambda x: x - 2
}.get(whatToUse, lambda x: x - 22)(value)
Onde
.get('c', lambda x: x - 22)(23)
Olha para cima "lambda x: x - 2"
no dict e usa-o com x=23
.get('xxx', lambda x: x - 22)(44)
Não o encontra no dict e usa o padrão "lambda x: x - 22"
com x=44
.
# 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
Uma vez que a variável x
deve ser usada duas vezes, modifiquei as funções lambda para sem parâmetros.
Eu tenho que correr com 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'
Edit: reparei que posso usar None
o tipo com dicionários. Para que isto emulasse switch ; case else
Primeiro, a FAQ oficial em Python cobre isto, e recomenda a cadeia elif
para casos simples e a dict
para casos maiores ou mais complexos. Também sugere um conjunto de métodos visit_
(um estilo usado por muitos frameworks de servidores) para alguns casos:
def dispatch(self, value):
method_name = 'visit_' + str(value)
method = getattr(self, method_name)
method()
A FAQ também menciona o PEP 275, que foi escrito para obter um oficial de uma vez por todas. decisão sobre a adição de C-style switch declarações. Mas esse PEP foi realmente adiado para o Python 3, e foi apenas oficialmente rejeitado como uma proposta separada, PEP 3103. A resposta foi, naturalmente, não-mas os dois PEPs têm links para informações adicionais se você estiver interessado nas razões ou na história.
Uma coisa que surgiu várias vezes (e pode ser vista em PEP 275, apesar de ter sido cortada como uma recomendação real) é que se você está realmente incomodado por ter 8 linhas de código para lidar com 4 casos, vs. as 6 linhas que você teria em C ou Bash, você pode sempre escrever isto:
if x == 1: print('first')
elif x == 2: print('second')
elif x == 3: print('third')
else: print('did not place')
Isto não é propriamente encorajado pelo PEP 8, mas é legível e não muito indiomático.
Ao longo de mais de uma década desde que a PEP 3103 foi rejeitada, a questão das declarações de casos em estilo C, ou mesmo a versão um pouco mais poderosa em Go, tem sido considerada morta; sempre que alguém o traz para cima em python-ideas ou-dev, eles são referidos ao velho decisao. No entanto, a ideia de uma correspondência completa dos padrões do tipo ML surge de poucos em poucos anos, especialmente porque línguas como a Swift e a Rust a adoptaram. O problema é que é difícil obter muito uso fora da correspondência de padrões sem tipos de dados algébricos. Embora o Guido tenha sido compreensivo com a ideia, ninguém apresentou uma proposta que se encaixe muito bem no Python. (Você pode ler meu strawman 2014 por exemplo.) Esta situação poderia mudar com {[5] } em 3.7 e algumas propostas esporádicas para um
enum
mais poderoso para lidar com tipos de soma, ou com várias propostas para diferentes tipos de declarações-encadernações locais (como PEP 3150 , ou o conjunto de propostas atualmente em discussão on-ideas). Mas até agora, não aconteceu.
Há também, ocasionalmente, propostas para a correspondência Perl 6, o que é basicamente uma mistura de tudo, desde {[[2]} até à troca de tipo de expedição única.
def f(x):
return 1 if x == 'a' else\
2 if x in 'bcd' else\
0 #default
Curto e fácil de ler, tem um valor padrão e suporta expressões em ambas as condições e valores de retorno.
No entanto, é menos eficiente do que a solução com um dicionário. Por exemplo, Python tem que analisar todas as condições antes de retornar o valor padrão.Acho que a melhor maneira é usar a linguagem python para manter o seu código testável. Como mostrado em respostas anteriores, eu uso dicionários para tirar vantagem das estruturas e linguagem python e manter o código de" caso " isolado em diferentes métodos. Abaixo está uma classe, mas você pode usar diretamente um módulo, globals e funções. A classe tem métodos que podem ser testados com isolamento . Dependendo das suas necessidades, você pode jogar com métodos estáticos e atributos também.
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)
É possível tirar partido deste método usando também as classes como chaves de "__escolha_Tabela". Desta forma você pode evitar é abuso de substâncias e manter tudo limpo e testável.
Suponha que você tem que processar um monte de mensagens ou pacotes a partir da rede ou do seu MQ. Cada pacote tem sua própria estrutura e seu código de gestão (de uma forma genérica). Com o código acima é possível fazer algo assim:
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)
Então ... a complexidade não é espalhada no fluxo de código, mas é renderizada na estrutura de código.
Fiz esta pequena e limpa solução
result = {
'case1': foo1,
'case2': foo2,
'case3': foo3,
'default': default,
}.get(option)()
Onde o foo1 (), o foo2 (), o foo3 () e o default () são funções
Definição:
def switch1(value, options):
if value in options:
options[value]()
Permite-lhe usar uma sintaxe bastante simples, com os casos agrupados num mapa:
def sample1(x):
local = 'betty'
switch1(x, {
'a': lambda: print("hello"),
'b': lambda: (
print("goodbye," + local),
print("!")),
})
Continuei a tentar redefinir o interruptor de uma forma que me permitisse livrar-me do "lambda", mas desisti. Alterando a definição:
def switch(value, *maps):
options = {}
for m in maps:
options.update(m)
if value in options:
options[value]()
elif None in options:
options[None]()
Permitiu-me mapear vários casos para o mesmo código, e fornecer uma opção predefinida:
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 tem de estar no seu próprio dicionário; switch () consolida os dicionários antes de procurar valor. Ainda é mais feio do que eu gostaria, mas tem a eficiência básica de usar um olhar hashed na expressão, em vez de um loop através de todas as chaves.
def numbers_to_strings(argument):
switcher = {
0: "zero",
1: "one",
2: "two",
}
return switcher.get(argument, "nothing")
Este código é análogo a:
function(argument){
switch(argument) {
case 0:
return "zero";
case 1:
return "one";
case 2:
return "two";
default:
return "nothing";
}
}
Verifique a fonte Para mais informações sobre o mapeamento de dicionário para funções.
Uma solução que tenho tendência a usar e que também faz uso de dicionários é:
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)()
Isto tem a vantagem de não tentar avaliar as funções todas as vezes, e você só tem que garantir que a função externa recebe toda a informação que as funções internas precisam.
Inspirado poresta resposta impressionante . Não requer código externo. Não foi testado. Cair não funciona bem.
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()
Se não se preocupar em perder o realce de sintaxe dentro das suites do caso, pode fazer o seguinte:
exec {
1: """
print ('one')
""",
2: """
print ('two')
""",
3: """
print ('three')
""",
}.get(value, """
print ('None')
""")
Onde value
é o valor. Em C, isto seria:
switch (value) {
case 1:
printf("one");
break;
case 2:
printf("two");
break;
case 3:
printf("three");
break;
default:
printf("None");
break;
}
Também podemos criar uma função auxiliar para fazer isto:
def switch(value, cases, default):
exec cases.get(value, default)
Então podemos usá - lo assim, por exemplo, com um, dois e três:
switch(value, {
1: """
print ('one')
""",
2: """
print ('two')
""",
3: """
print ('three')
""",
}, """
print ('None')
""")
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
Isto pode então ser usado com o @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
A boa notícia é que isto já foi feito no módulo NeoPySwitch. Instalar simplesmente com o pip:
pip install NeoPySwitch