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 switch
ou case
, mas o Python não parece ter uma switch
instrução. Quais são as soluções Python recomendadas nesse cenário?
python
switch-statement
Michael Schneider
fonte
fonte
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 umaswitch
declaração geral ou apenas para o caso de retornar valores sem a possibilidade de executar partes gerais do código.Respostas:
Você poderia usar um dicionário:
fonte
get
método provavelmente seria mais normal do que usar umcollections.defaultdict
nesse caso.}.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!)Se desejar padrões, use o
get(key[, default])
método do dicionário :fonte
Eu sempre gostei de fazer dessa maneira
Daqui
fonte
.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'.[value]
, que retornará apenas uma das 3 funções (supondo quevalue
seja uma das 3 teclas). A função ainda não foi chamada nesse ponto. Em seguida,(x)
chama a função recém-retornada comx
como argumento (e o resultado vai pararesult
). As outras 2 funções não serão chamadas.Além dos métodos de dicionário (que eu realmente gosto, BTW), você também pode usar
if
-elif
-else
para obter a funcionalidadeswitch
/case
/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
break
declaração, mas pode ter um teste mais complicado. Sua formatação é mais agradável que uma série deif
s aninhados , embora funcionalmente seja isso o que está mais próximo.fonte
get
caminho, mas o caminho padrão é simplesmente mais legível.x = the.other.thing
antes. Normalmente, você teria um único elif múltiplo e outro único, como é mais fácil de entender.if/elif/else
?x in 'bc'
, tenha em mente que"" in "bc"
éTrue
.Minha receita favorita do Python para switch / case é:
Curto e simples para cenários simples.
Compare com mais de 11 linhas de código C:
Você pode até atribuir várias variáveis usando tuplas:
fonte
default = -1; result = choices.get(key, default)
.result=key=='a'?1:key==b?2:-1
result = 1 if key == 'a' else (2 if key == 'b' else 'default')
. mas o liner é legível?Uso:
Testes:
fonte
if
declaração padrão ?case(2)
bloco chamou outra função que usa switch (), ao fazercase(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 .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.
Aqui está um exemplo:
fonte
for case in switch()
comwith switch() as case
, faz mais sentido, pois ele precisa ser executado apenas uma vez.with
isso não permitebreak
, então a opção de avanço é retirada.if c in set(range(0,9)): print "digit" elif c in set(map(chr, range(ord('a'), ord('z')))): print "lowercase"
?fonte
value
propriedade (pública) à classe Switch para que você possa fazer referência àcase.value
dentro da instruçãoHá um padrão que aprendi com o código Twisted Python.
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 enviariaself.state
. Essa opção pode ser estendida de maneira limpa, herdando da classe base e definindo seus própriosdo_
métodos. Muitas vezes, você nem sequer temdo_
métodos na classe base.Edit: como exatamente isso é usado
No caso de SMTP, você receberá
HELO
do cabo. O código relevante (detwisted/mail/smtp.py
, modificado para o nosso caso) é semelhante a esteVocê receberá
' HELO foo.bar.com '
(ou poderá receber'QUIT'
ou'RCPT TO: foo'
). Isso é tokenizado emparts
como['HELO', 'foo.bar.com']
. O nome da pesquisa de método real é obtidoparts[0]
.(O método original também é chamado
state_COMMAND
, porque usa o mesmo padrão para implementar uma máquina de estado, por exemplogetattr(self, 'state_' + self.mode)
)fonte
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:
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:
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.
fonte
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
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"
fonte
expandindo a idéia de "ditar como interruptor". se você deseja usar um valor padrão para seu switch:
fonte
'default'
) à regra (obtenha algo com esse ditado). Por design, os programas Python usam exceções rapidamente. Dito isto, o usoget
pode potencialmente tornar o código um pouco melhor.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.
fonte
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:
que pode ser usado assim:
então espy traduza-o em Python como:
fonte
while True:
parte superior do código Python gerado? Isso inevitavelmente atingirá abreak
parte inferior do código Python gerado, portanto, parece-me que owhile True:
ebreak
poderia ser removido. Além disso, o ESPY é inteligente o suficiente para alterar o nomecont
se 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.while True:
ebreak
s é permitir, mas não exigem a queda-through.Eu descobri que uma estrutura de switch comum:
pode ser expresso em Python da seguinte maneira:
ou formatado de forma mais clara:
Em vez de ser uma declaração, a versão python é uma expressão que é avaliada como um valor.
fonte
parameter
ep1==parameter
f = lambda x: 'a' if x==0 else 'b' if x==1 else 'c'
. Quando liguei mais tardef(2)
, recebi'c'
;f(1)
,'b'
; ef(0)
,'a'
. Quanto a p1 (x), denota um predicado; desde que retorneTrue
ouFalse
, não importa se é uma chamada de função ou uma expressão, tudo bem.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
elif
cadeia para casos simples edict
para casos maiores ou mais complexos. Também sugere um conjunto devisit_
métodos (um estilo usado por muitas estruturas de servidor) para alguns casos: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:
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
dataclass
item 3.7 e com algumas propostas esporádicas para uma forma mais eficienteenum
de 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
elif
regex até comutação de tipo de despacho único.fonte
Solução para executar funções:
onde foo1 (), foo2 (), foo3 () e padrão () são funções
fonte
foo2()
, asfoo1()
,foo3()
edefault()
funções são também indo para executar, ou seja, as coisas podem levar um longo tempoget(option)()
. problema resolvido.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.
Fornece:
fonte
break
no final do código para acase
. Mas eu acho que nós não precisamos de tais "fallthrough" :)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.
Onde
procura
"lambda x: x - 2"
no ditado e o usa comx=23
não o encontra no dict e usa o padrão
"lambda x: x - 22"
comx=44
.fonte
fonte
fonte
Gostei da resposta de Mark Bies
Como a
x
variável deve ser usada duas vezes, modifiquei as funções lambda para sem parâmetros.Eu tenho que correr com
results[value](value)
Editar: notei que posso usar o
None
tipo com dicionários. Então isso simulariaswitch ; case else
fonte
result[None]()
?result = {'a': 100, None:5000}; result[None]
None:
se comportadefault:
.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.
fonte
você pode usar um ditado despachado:
Resultado:
fonte
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,
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?) seexpression
avalia para1
,Was 2.
seexpression
avalia para2
ouWas something.
seexpression
avalia para outra coisa.fonte
Definindo:
permite que você use uma sintaxe bastante direta, com os casos agrupados em um mapa:
Continuei tentando redefinir a opção de uma maneira que me permitisse me livrar do "lambda:", mas desisti. Ajustando a definição:
Permitiu-me mapear vários casos para o mesmo código e fornecer uma opção padrão:
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.
fonte
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.
É 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:
Portanto, a complexidade não é espalhada no fluxo de código, mas é renderizada na estrutura do código .
fonte
Expandindo a resposta de Greg Hewgill - Podemos encapsular a solução de dicionário usando um decorador:
Isso pode ser usado com o
@case
-decoratorA boa notícia é que isso já foi feito no módulo NeoPySwitch. Basta instalar usando o pip:
fonte
Uma solução que costumo usar e que também usa dicionários é:
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.
fonte
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:
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:
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.
fonte