Substituições para a instrução switch em Python?


1718

Eu quero escrever uma função em Python que retorna diferentes valores fixos com base no valor de um índice de entrada.

Em outros idiomas, eu usaria uma instrução switchou case, mas o Python não parece ter uma switchinstrução. Quais são as soluções Python recomendadas nesse cenário?


77
PEP relacionado, de autoria do próprio Guido: PEP 3103
chb

28
@chb Nesse PEP, Guido não menciona que as cadeias if / elif também são uma fonte clássica de erro. É uma construção muito frágil.
itsbruce

15
Faltando em todas as soluções aqui está a detecção de valores de casos duplicados . Como princípio de falha rápida, isso pode ser uma perda mais importante do que o desempenho ou o recurso inovador.
Bob Stein

6
switché realmente mais "versátil" do que algo retornando diferentes valores fixos com base no valor de um índice de entrada. Ele permite que diferentes partes do código sejam executadas. Na verdade, ele nem precisa retornar um valor. Gostaria de saber se algumas das respostas aqui são boas substituições para uma switchdeclaração geral ou apenas para o caso de retornar valores sem a possibilidade de executar partes gerais do código.
sancho.s ReinstateMonicaCellio 14/05

3
@ MalikA.Rumi Construção frágil, assim como um loop while é uma construção frágil se você tentar usá-lo para fazer o que ... em ... faz. Você vai chamar os programadores fracos de usar loops? Enquanto loops são tudo o que eles realmente precisam. Porém, para os loops mostrarem uma intenção clara, salve o clichê inútil e dê a oportunidade de criar abstrações poderosas.
itsbruce

Respostas:


1486

Você poderia usar um dicionário:

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

100
O que acontece se x não for encontrado?
Nick

46
@ Nick: você pode usar defaultdict
Eli Bendersky

385
Eu recomendo colocar o exterior dict da função se o desempenho for um problema, por isso não re-construir o dict em cada chamada de função
Claudiu

56
@ EliBendersky, Usar o getmétodo provavelmente seria mais normal do que usar um collections.defaultdictnesse caso.
Mike Graham

27
@ Nick, uma exceção é lançada - em }.get(x, default)vez disso, se houver um padrão. (Nota: este é um agradável muito do que o que acontece se você deixar o default fora de uma instrução switch!)
Mike Graham

1375

Se desejar padrões, use o get(key[, default])método do dicionário :

def f(x):
    return {
        'a': 1,
        'b': 2
    }.get(x, 9)    # 9 is default if x not found

11
E se 'a' e 'b' corresponderem a 1 e 'c' e 'd' corresponderem a 2?
quer

13
@JM: Bem, obviamente as pesquisas de dicionário não suportam falhas. Você pode fazer uma pesquisa dupla no dicionário. Ou seja, 'a' e 'b' apontam para a resposta1 e 'c' e 'd' apontam para a resposta2, que estão contidas em um segundo dicionário.
Nick

3
é melhor passar um valor padrão
HaTiMSuM 13/10

Existe um problema com essa abordagem, primeiro sempre que você chamar f e criar o ditado novamente, segundo se tiver um valor mais complexo, poderá obter uma exceção ex. se x é uma tupla e queremos fazer algo assim x = ('a') def f (x): retorna {'a': x [0], 'b': x [1]} .get ( x [0], 9) Isso aumentará IndexError
Idan Haim Shalom

2
@Idan: A questão era replicar o switch. Tenho certeza de que também poderia quebrar esse código se tentasse colocar valores ímpares. Sim, ele será recriado, mas é simples de corrigir.
Nick

394

Eu sempre gostei de fazer dessa maneira

result = {
  'a': lambda x: x * 5,
  'b': lambda x: x + 7,
  'c': lambda x: x - 2
}[value](x)

Daqui


16
ótimo método, combinado com get () para o padrão de alça é a minha melhor escolha também
drAlberT

27
talvez não seja uma boa ideia usar lambda nesse caso, porque na verdade o lambda é chamado toda vez que o dicionário é criado.
Asher

13
Infelizmente, este é o mais próximo que as pessoas vão chegar. Os métodos que usam .get()(como as respostas mais altas atuais) precisarão avaliar todas as possibilidades antes do envio e, portanto, não são apenas (não apenas muito, mas) extremamente ineficientes e também não podem ter efeitos colaterais; esta resposta contorna esse problema, mas é mais detalhada. Eu usaria if / elif / else, e mesmo aqueles demoram tanto tempo para escrever como 'case'.
Ninjagecko 17/03/2014

13
isso não avaliaria todas as funções / lambdas sempre em todos os casos, mesmo que esteja retornando apenas um dos resultados?
Slf

23
@slf Não, quando o fluxo de controle atingir esse trecho de código, ele criará 3 funções (através do uso das 3 lambdas) e, em seguida, criará um dicionário com essas 3 funções como valores, mas elas permanecerão desnecessárias (a avaliação é um pouco ambígua em nesse contexto) em primeiro lugar. Em seguida, o dicionário é indexado via [value], que retornará apenas uma das 3 funções (supondo que valueseja uma das 3 teclas). A função ainda não foi chamada nesse ponto. Em seguida, (x)chama a função recém-retornada com xcomo argumento (e o resultado vai para result). As outras 2 funções não serão chamadas.
precisa saber é o seguinte

354

Além dos métodos de dicionário (que eu realmente gosto, BTW), você também pode usar if- elif- elsepara 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

Obviamente, isso não é idêntico ao switch / case - você não pode ter uma falha tão fácil quanto deixar de fora a breakdeclaração, mas pode ter um teste mais complicado. Sua formatação é mais agradável que uma série de ifs aninhados , embora funcionalmente seja isso o que está mais próximo.


51
i realmente prefiro isso, ele usa um construtor de linguagem Standart e não jogar um KeyError se nenhum caso for encontrado
martyglaubitz

7
Pensei no dicionário / getcaminho, mas o caminho padrão é simplesmente mais legível.
Martin Thoma

2
@someuser, mas o fato de que eles podem "se sobrepor" é um recurso. Você apenas garante que o pedido seja a prioridade na qual as correspondências devem ocorrer. Quanto ao x repetido: basta fazer um x = the.other.thingantes. Normalmente, você teria um único elif múltiplo e outro único, como é mais fácil de entender.
Matthew Schinckel 03/03

7
Bom, o "Fall-through por não usar elif" é um pouco confuso, no entanto. E quanto a isso: esqueça o "fall through" e aceite-o como dois if/elif/else?
Alois Mahdal

7
Também vale a pena mencionar, quando usar coisas como x in 'bc', tenha em mente que "" in "bc"é True.
Lohmar ASHAR 28/08/18

185

Minha receita favorita do Python para switch / case é:

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

Curto e simples para cenários simples.

Compare com mais de 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;
}

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

16
Acho que essa é uma resposta mais robusta que a aceita.
cerd

3
@ algum usuário: C requer que o valor de retorno seja do mesmo tipo para todos os casos. Python não. Eu queria destacar essa flexibilidade do Python caso alguém tivesse uma situação que justificasse esse uso.
ChaimG

3
@ algum usuário: Pessoalmente, acho que {} .get (,) é legível. Para legibilidade extra para iniciantes em Python, você pode querer usar default = -1; result = choices.get(key, default).
ChaimG

4
comparar com 1 linha de c ++result=key=='a'?1:key==b?2:-1
Jasen

4
@Jasen pode-se argumentar que você pode fazê-lo em uma linha de Python, assim: result = 1 if key == 'a' else (2 if key == 'b' else 'default'). mas o liner é legível?
ChaimG

101
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

Testes:

n = 2
#Result:
#n is an even number.
#n is a prime number.
n = 11
#Result:
#Only single-digit numbers are allowed.

64
Isso não é seguro para ameaças. Se várias opções forem atingidas ao mesmo tempo, todas as opções receberão o valor da última opção.
francescortiz

48
Embora @francescortiz provavelmente signifique segurança de thread, também não é seguro contra ameaças. Ameaça os valores das variáveis!
Zizouz212

7
Provavelmente, o problema de segurança do encadeamento poderia ser contornado usando o armazenamento local do encadeamento . Ou poderia ser totalmente evitado retornando uma instância e usando-a para as comparações de casos.
precisa saber é o seguinte

6
@blubberdiblub Mas então, não é apenas mais eficiente usar uma ifdeclaração padrão ?
Wizzwizz4

9
Isso também não é seguro se usado em várias funções. No exemplo dado, se o case(2)bloco chamou outra função que usa switch (), ao fazer case(2, 3, 5, 7)etc para procurar o próximo caso a ser executado, ele usará o valor do switch definido pela outra função e não o definido pela instrução switch atual .
user9876

52

O meu favorito é uma receita muito boa . Você realmente vai gostar. É o mais próximo que eu já vi de declarações de casos de comutação reais, especialmente em recursos.

class switch(object):
    def __init__(self, value):
        self.value = value
        self.fall = False

    def __iter__(self):
        """Return the match method once, then stop"""
        yield self.match
        raise StopIteration

    def match(self, *args):
        """Indicate whether or not to enter a case suite"""
        if self.fall or not args:
            return True
        elif self.value in args: # changed for v1.5, see below
            self.fall = True
            return True
        else:
            return False

Aqui está um exemplo:

# 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!"

3
Eu substituiria for case in switch()com with switch() as case, faz mais sentido, pois ele precisa ser executado apenas uma vez.
Ski

4
@Skirmantas: Observe que withisso não permite break, então a opção de avanço é retirada.
Jonas Schäfer

5
Desculpas por não me esforçar mais para determinar isso: uma resposta semelhante acima não é segura para threads. É isto?
David Winiecki

1
@DavidWiniecki Os componentes de código ausentes do item acima (e possivelmente os direitos autorais do activestate) parecem ser seguros para threads.
Jasen

seria outra versão disso if c in set(range(0,9)): print "digit" elif c in set(map(chr, range(ord('a'), ord('z')))): print "lowercase"?
mpag 31/10/16

51
class Switch:
    def __init__(self, value):
        self.value = value

    def __enter__(self):
        return self

    def __exit__(self, type, value, traceback):
        return False # Allows a traceback to occur

    def __call__(self, *values):
        return self.value in values


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

9
Usar gerenciadores de contexto é uma boa solução criativa. Eu recomendo adicionar um pouco de explicação e talvez um link para algumas informações sobre os Gerentes de Contexto, para dar a este post algum contexto, bem;)
Will

2
Eu não gosto muito de se / elif encadeia, mas essa é a mais criativa e a mais prática de todas as soluções que já vi usando a sintaxe existente do Python.
itsbruce

2
Isso é muito legal. Uma melhoria sugerida é adicionar uma valuepropriedade (pública) à classe Switch para que você possa fazer referência à case.valuedentro da instrução
Peter

48

Há um padrão que aprendi com o 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'

Você pode usá-lo sempre que precisar despachar em um token e executar trecho de código estendido. Em uma máquina de estado, você teria state_métodos e enviaria self.state. Essa opção pode ser estendida de maneira limpa, herdando da classe base e definindo seus próprios do_métodos. Muitas vezes, você nem sequer tem do_métodos na classe base.

Edit: como exatamente isso é usado

No caso de SMTP, você receberá HELOdo cabo. O código relevante (de twisted/mail/smtp.py, modificado para o nosso caso) é semelhante a 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

Você receberá ' HELO foo.bar.com '(ou poderá receber 'QUIT'ou 'RCPT TO: foo'). Isso é tokenizado em partscomo ['HELO', 'foo.bar.com']. O nome da pesquisa de método real é obtido parts[0].

(O método original também é chamado state_COMMAND, porque usa o mesmo padrão para implementar uma máquina de estado, por exemplo getattr(self, 'state_' + self.mode))


4
Não vejo o benefício desse padrão em chamar os métodos diretamente: SMTP (). Do_HELO ('foo.bar.com') OK, pode haver código comum no lookupMethod, mas, como isso também pode ser substituído por a subclasse, não vejo o que você ganha com a indireção.
Mr Shark

1
Você não saberia qual método chamar antecipadamente, ou seja, 'HELO' vem de uma variável. Eu adicionei um exemplo de uso à postagem original

Gostaria de sugerir simplesmente: eval ('SMTP (). Do_' + comando) ('foo.bar.com')
jforberg

8
avaliação? a sério? e, em vez de instanciar um método por chamada, podemos instanciar muito bem uma vez e usá-lo em todas as chamadas, desde que não possua um estado interno.
Mahesh

1
Na IMO, a chave real aqui é o envio usando getattr para especificar uma função a ser executada. Se os métodos estivessem em um módulo, você poderia executar getattr (locals (), func_name) para obtê-lo. A parte 'do_' é boa para segurança / erros, portanto, somente funções com o prefixo podem ser chamadas. O próprio SMTP chama lookupMethod. Idealmente, o exterior não sabe nada disso. Realmente não faz sentido executar SMTP (). LookupMethod (nome) (dados). Como o comando e os dados estão em uma sequência e o SMTP os analisa, isso faz mais sentido. Por fim, o SMTP provavelmente possui outro estado compartilhado que justifica ser uma classe.
ShawnFumo

27

Digamos que você não queira apenas retornar um valor, mas deseja usar métodos que alteram algo em um objeto. Usando a abordagem aqui indicada seria:

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. Portanto, mesmo que seu valor seja 'a', o objeto será incrementado 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. Dessa forma, somente o ponteiro da função e a lista de argumentos são retornados, não avaliados. 'result' avalia a chamada de função retornada.


23

Eu só vou colocar meus dois centavos aqui. A razão pela qual não existe uma declaração case / switch no Python é porque o Python segue o princípio de 'Existe apenas uma maneira correta de fazer alguma coisa'. Então, obviamente, você poderia criar várias maneiras de recriar a funcionalidade de comutação / caixa, mas a maneira Pythonic de realizar isso é a construção if / elif. ie

if something:
    return "first thing"
elif somethingelse:
    return "second thing"
elif yetanotherthing:
    return "third thing"
else:
    return "default thing"

Eu apenas senti que o PEP 8 merecia um aceno aqui. Uma das coisas bonitas do Python é sua simplicidade e elegância. Isso é amplamente derivado dos princípios estabelecidos no PEP 8, incluindo "Existe apenas uma maneira correta de fazer alguma coisa"


6
Então, por que o Python tem para loops e loops while? Tudo o que você pode fazer com um loop for pode implementar com um loop while.
itsbruce

1
Verdade. Switch / case são frequentemente abusados ​​pelos programadores iniciantes. O que eles realmente querem é o padrão de estratégia .
user228395

Soa como desejos Python era Clojure
TWR Cole

1
@TWRCole Acho que não, o Python estava fazendo isso primeiro. Python tem sido em torno desde 1990 e Clojure desde 2007.
Taylor

Existe apenas uma maneira certa de fazer algo. Python 2.7 ou Python 3? Ri muito.
TWR Cole

17

expandindo a idéia de "ditar como interruptor". se você deseja usar um valor padrão para seu switch:

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

14
Eu acho que é mais claro usar .get () no dict com o padrão especificado. Prefiro deixar Exceptions em circunstâncias excepcionais, e ele corta três linhas de código e um nível de indentação sem ser obscuro.
Chris B.

10
Esta é uma circunstância excepcional. Pode ou não ser uma circunstância rara , dependendo de útil, mas é definitivamente uma exceção (volte para trás 'default') à regra (obtenha algo com esse ditado). Por design, os programas Python usam exceções rapidamente. Dito isto, o uso getpode potencialmente tornar o código um pouco melhor.
Mike Graham

16

Se você tiver um bloco de casos complicado, considere usar uma tabela de pesquisa de dicionário de funções ...

Se você não fez isso antes, é uma boa ideia entrar no depurador e ver exatamente como o dicionário pesquisa cada função.

NOTA: Do não usar "()" no interior da caixa / pesquisa de dicionário ou ele vai chamar cada uma de suas funções como o bloco de dicionário / case é criado. Lembre-se disso, porque você deseja chamar cada função apenas uma vez usando uma pesquisa 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()

Eu gosto da sua solução. Mas, e se eu apenas precisar passar algumas variáveis ​​ou objetos?
Tedo Vrbanec

Isso não funcionará se o método estiver esperando parâmetros.
Kulasangar 29/01

16

Se você está pesquisando declarações extras, como "switch", criei um módulo python que estende o Python. Chama-se ESPY como "Estrutura aprimorada para Python" e está disponível para o Python 2.xe o Python 3.x.

Por exemplo, nesse caso, uma instrução switch 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 traduza-o 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

Muito legal, mas qual é o objetivo da while True:parte superior do código Python gerado? Isso inevitavelmente atingirá a breakparte inferior do código Python gerado, portanto, parece-me que o while True:e breakpoderia ser removido. Além disso, o ESPY é inteligente o suficiente para alterar o nome contse o usuário usar esse mesmo nome em seu próprio código? De qualquer forma, eu quero usar o Python baunilha para não usá-lo, mas é legal mesmo assim. +1 por pura frescura.
ArtOfWarfare

@ArtOfWarfare A razão para o while True:e breaks é permitir, mas não exigem a queda-through.
Solomon Ucko

Este módulo ainda está disponível?
Solomon Ucko

15

Eu descobri que uma estrutura de switch comum:

switch ...parameter...
case p1: v1; break;
case p2: v2; break;
default: v3;

pode ser expresso em Python da seguinte maneira:

(lambda x: v1 if p1(x) else v2 if p2(x) else v3)

ou formatado de 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 python é uma expressão que é avaliada como um valor.


Além disso, em vez de ... parâmetro ... e p1 (x) como sobre parameterep1==parameter
Bob Stein

@ BobStein-VisiBone oi, aqui é um exemplo que é executado em minha sessão python: f = lambda x: 'a' if x==0 else 'b' if x==1 else 'c'. Quando liguei mais tarde f(2), recebi 'c'; f(1), 'b'; e f(0), 'a'. Quanto a p1 (x), denota um predicado; desde que retorne Trueou False, não importa se é uma chamada de função ou uma expressão, tudo bem.
leo

@ BobStein-VisiBone Sim, você está certo! Obrigado :) Para que a expressão de várias linhas funcione, parênteses devem ser colocados, como na sua sugestão ou no meu exemplo modificado.
leo

Excelente. Agora vou excluir todos os meus comentários sobre os parênteses.
21415 Bob Stein

15

A maioria das respostas aqui são bastante antigas, e especialmente as aceitas, por isso parece valer a pena atualizar.

Primeiro, o FAQ oficial do Python aborda isso e recomenda a elifcadeia para casos simples e dictpara casos maiores ou mais complexos. Também sugere um conjunto de visit_métodos (um estilo usado por muitas estruturas de servidor) para alguns casos:

def dispatch(self, value):
    method_name = 'visit_' + str(value)
    method = getattr(self, method_name)
    method()

O FAQ também menciona PEP 275 , que foi escrito para obter uma decisão oficial e definitiva sobre a adição de instruções de chave no estilo C. Mas esse PEP foi realmente adiado para o Python 3, e só foi oficialmente rejeitado como uma proposta separada, o PEP 3103 . A resposta foi, é claro, não - mas os dois PEPs têm links para informações adicionais, se você estiver interessado nos motivos ou na história.


Uma coisa que surgiu várias vezes (e pode ser vista no PEP 275, mesmo que tenha sido cortada como uma recomendação real) é que, se você realmente se incomoda ao ter 8 linhas de código para lidar com 4 casos, em comparação com os 6 linhas que você teria em C ou Bash, você sempre pode escrever isso:

if x == 1: print('first')
elif x == 2: print('second')
elif x == 3: print('third')
else: print('did not place')

Isso não é exatamente incentivado pelo PEP 8, mas é legível e não muito unidiomatic.


Durante mais de uma década desde que o PEP 3103 foi rejeitado, a questão das declarações de caso no estilo C, ou mesmo a versão um pouco mais poderosa em Go, foi considerada morta; sempre que alguém aborda ideias de python ou -dev, elas são encaminhadas para a decisão antiga.

No entanto, a idéia de uma correspondência completa de padrões no estilo ML surge a cada poucos anos, principalmente porque idiomas como Swift e Rust a adotaram. O problema é que é difícil tirar muito proveito da correspondência de padrões sem os tipos de dados algébricos. Embora Guido tenha sido solidário com a idéia, ninguém apresentou uma proposta que se encaixe muito bem no Python. (Você pode ler meu exemplo de 2014 para um exemplo.) Isso pode mudar com o dataclassitem 3.7 e com algumas propostas esporádicas para uma forma mais eficiente enumde lidar com tipos de soma, ou com várias propostas para diferentes tipos de ligações locais de declaração (como PEP 3150 ou o conjunto de propostas atualmente sendo discutidas em idéias). Mas até agora, não tem.

Ocasionalmente, também existem propostas para a correspondência no estilo Perl 6, que é basicamente uma confusão de tudo, desde elifregex até comutação de tipo de despacho único.


15

Solução para executar funções:

result = {
    'case1':     foo1, 
    'case2':     foo2,
    'case3':     foo3,
    'default':   default,
}.get(option)()

onde foo1 (), foo2 (), foo3 () e padrão () são funções


1
Sim, por exemplo, se sua opção variável == "case2" seu resultado = foo2 ()
Alejandro Quintanar

e assim por diante.
Alejandro Quintanar

Sim, eu entendo o propósito. Mas a minha preocupação é que, se você só deseja foo2(), as foo1(), foo3()e default()funções são também indo para executar, ou seja, as coisas podem levar um longo tempo
Brian Underwood

1
omita o () dentro do dicionário. use get(option)(). problema resolvido.
timgeb

1
Excelente a utilização de () é uma solução de grelha, que feita uma essência para testá-lo gist.github.com/aquintanar/01e9920d8341c5c6252d507669758fe5
Alejandro Quintanar

13

Não encontrei a resposta simples que estava procurando em nenhum lugar na pesquisa do Google. Mas eu descobri mesmo assim. É realmente bem simples. Decidiu publicá-lo e talvez impedir alguns arranhões a menos na cabeça de outra pessoa. A chave é simplesmente "in" e tuplas. Aqui está o comportamento da instrução switch com fall-through, incluindo fall-through RANDOM.

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)

Fornece:

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.

Onde exatamente está o avanço aqui?
Jonas Schäfer

Opa! Há uma queda por lá, mas não estou mais contribuindo para o Stack Overflow. Não gosto deles. Gosto das contribuições de outras pessoas, mas não do Stackoverflow. Se você estiver usando o fall through for FUNCTIONALITY, deseja capturar determinadas condições em uma instrução de caso único em um comutador (um catch all), até chegar a uma instrução de interrupção em um comutador.
JD Graham

2
Aqui, os valores "Dog" e "Cat" caem e são tratados pela mesma funcionalidade, que é definida como tendo "quatro pernas". É um equivalente ABSTRATO a cair e valores diferentes manipulados pela instrução SAME case onde ocorre uma interrupção.
JD Graham

@JDGraham Acho que Jonas significou outro aspecto da inovação, que acontece quando o programador ocasionalmente esquece de escrever breakno final do código para a case. Mas eu acho que nós não precisamos de tais "fallthrough" :)
Mikhail Batcer

12

As soluções que eu uso:

Uma combinação de duas das soluções postadas aqui, que é relativamente fácil de ler e suporta padrões.

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)

procura "lambda x: x - 2"no ditado e o usa comx=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.


10
# 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

10
def f(x):
    dictionary = {'a':1, 'b':2, 'c':3}
    return dictionary.get(x,'Not Found') 
##Returns the value for the letter x;returns 'Not Found' if x isn't a key in the dictionary

Considere incluir uma breve descrição do seu código e como ele resolve a pergunta postada
Henry Woody

Ok, eu adicionei um comentário para isso agora.
Vikhyat Agarwal 30/10

8

Gostei da resposta de Mark Bies

Como a xvariável 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'

Editar: notei que posso usar o Nonetipo com dicionários. Então isso simulariaswitch ; case else


O caso Nenhum não simula simplesmente result[None]()?
21415 Bob Stein

Sim, exatamente. Quero dizerresult = {'a': 100, None:5000}; result[None]
guneysus 11/03/2015

4
Apenas verificando se ninguém está pensando None:se comporta default:.
21415 Bob Stein

7
def f(x):
     return 1 if x == 'a' else\
            2 if x in 'bcd' else\
            0 #default

Curto e fácil de ler, possui um valor padrão e suporta expressões em condições e valores de retorno.

No entanto, é menos eficiente que a solução com um dicionário. Por exemplo, o Python precisa varrer todas as condições antes de retornar o valor padrão.


7

você pode usar um ditado despachado:

#!/usr/bin/env python


def case1():
    print("This is case 1")

def case2():
    print("This is case 2")

def case3():
    print("This is case 3")


token_dict = {
    "case1" : case1,
    "case2" : case2,
    "case3" : case3,
}


def main():
    cases = ("case1", "case3", "case2", "case1")
    for case in cases:
        token_dict[case]()


if __name__ == '__main__':
    main()

Resultado:

This is case 1
This is case 3
This is case 2
This is case 1

6

Simples, não testado; cada condição é avaliada independentemente: não há falhas, mas todos os casos são avaliados (embora a expressão para ativar seja avaliada apenas uma vez), a menos que haja uma declaração de interrupção. Por exemplo,

for case in [expression]:
    if case == 1:
        print(end='Was 1. ')

    if case == 2:
        print(end='Was 2. ')
        break

    if case in (1, 2):
        print(end='Was 1 or 2. ')

    print(end='Was something. ')

impressões Was 1. Was 1 or 2. Was something. (Caramba! Por que não consigo deixar espaços em branco nos blocos de código embutido?) se expressionavalia para 1, Was 2.se expressionavalia para 2ou Was something.se expressionavalia para outra coisa.


1
Bem, a queda nos trabalhos, mas apenas para ir para do_default.
syockit

5

Definindo:

def switch1(value, options):
  if value in options:
    options[value]()

permite que você use uma sintaxe bastante direta, com os casos agrupados em um mapa:

def sample1(x):
  local = 'betty'
  switch1(x, {
    'a': lambda: print("hello"),
    'b': lambda: (
      print("goodbye," + local),
      print("!")),
    })

Continuei tentando redefinir a opção de uma maneira que me permitisse me livrar do "lambda:", mas desisti. Ajustando 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 padrão:

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 deve estar em seu próprio dicionário; switch () consolida os dicionários antes de procurar o valor. Ainda é mais feio do que eu gostaria, mas tem a eficiência básica de usar uma pesquisa de hash na expressão, em vez de um loop por todas as chaves.


5

Eu acho que a melhor maneira é usar os idiomas da linguagem python para manter seu código testável . Como mostrado nas respostas anteriores, eu uso dicionários para tirar proveito das estruturas e da linguagem python e manter o código "case" isolado em diferentes métodos. Abaixo há uma classe, mas você pode usar diretamente um módulo, globais e funções. A classe possui métodos que podem ser testados com isolamento . Dependendo de suas necessidades, você também pode jogar com métodos e 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)

É possível aproveitar esse método usando também classes como chaves de "__choice_table". Dessa forma, você pode evitar um abuso de instância e manter tudo limpo e testável.

Supondo que você precise processar muitas mensagens ou pacotes da rede ou do seu MQ. Cada pacote tem sua própria estrutura e seu código de gerenciamento (de maneira 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)

Portanto, a complexidade não é espalhada no fluxo de código, mas é renderizada na estrutura do código .


Muito feio ... a caixa do interruptor é muito limpa ao ler. Não consigo entender por que não está implementado no Python.
precisa saber é o seguinte

@AndyClifton: Me desculpe ... um exemplo? Pense em cada vez que você precisar ter vários códigos de ramificação de decisão e poderá aplicar esse método.
J_Zar

@ jmcollin92: a instrução switch é confortável, eu concordo. No entanto, o programador tende a escrever instruções e códigos muito longos que não são reutilizáveis. A maneira que eu descrevi é mais limpa para testar e mais reutilizável, IMHO.
J_Zar

@J_Zar: re. Minha solicitação de um exemplo: sim, entendi, mas estou lutando para colocar isso no contexto de um pedaço maior de código. Você poderia mostrar como eu poderia usar isso em uma situação do mundo real?
Andy Clifton

1
@ AndyClifton: Desculpe, estou atrasado, mas publiquei um exemplo de caso.
J_Zar

5

Expandindo a resposta de Greg Hewgill - Podemos encapsular a solução de dicionário usando um 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

Isso pode ser usado com o @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

A boa notícia é que isso já foi feito no módulo NeoPySwitch. Basta instalar usando o pip:

pip install NeoPySwitch

5

Uma solução que costumo usar e que também usa 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)()

Isso tem a vantagem de não tentar avaliar as funções todas as vezes, e você só precisa garantir que a função externa obtenha todas as informações que as funções internas precisam.


5

Até agora, existem muitas respostas que dizem: "não temos uma opção no Python, faça dessa maneira". No entanto, gostaria de salientar que a própria instrução switch é uma construção de fácil abuso que pode e deve ser evitada na maioria dos casos, porque promove uma programação lenta. Caso em questão:

def ToUpper(lcChar):
    if (lcChar == 'a' or lcChar == 'A'):
        return 'A'
    elif (lcChar == 'b' or lcChar == 'B'):
        return 'B'
    ...
    elif (lcChar == 'z' or lcChar == 'Z'):
        return 'Z'
    else:
        return None        # or something

Agora, você poderia fazer isso com uma instrução switch (se o Python oferecesse uma), mas estaria desperdiçando seu tempo porque existem métodos que fazem isso muito bem. Ou talvez, você tenha algo menos óbvio:

def ConvertToReason(code):
    if (code == 200):
        return 'Okay'
    elif (code == 400):
        return 'Bad Request'
    elif (code == 404):
        return 'Not Found'
    else:
        return None

No entanto, esse tipo de operação pode e deve ser tratado com um dicionário, pois será mais rápido, menos complexo, menos propenso a erros e mais compacto.

E a grande maioria dos "casos de uso" para instruções de troca se enquadra em um desses dois casos; há muito pouca razão para usar uma se você já pensou bem no seu problema.

Então, em vez de perguntar "como alterno no Python?", Talvez devêssemos perguntar: "por que quero alternar no Python?" porque essa é frequentemente a pergunta mais interessante e muitas vezes expõe falhas no design do que você estiver criando.

Agora, isso não quer dizer que os switches também nunca devam ser usados. Máquinas de estado, lexers, analisadores e autômatos os usam até certo ponto e, em geral, quando você inicia a partir de uma entrada simétrica e passa para uma saída assimétrica, eles podem ser úteis; você só precisa ter certeza de que não usa a chave como martelo, porque vê um monte de pregos no seu código.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.