O que é o equivalente de Switch-Case em Python? – DIG|ME
O que é o equivalente de Switch-Case em Python?
Historicamente, a sintaxe Python não tem uma declaração de caso de switch. Em 2006, Guido van Rossum, o autor original de Python, propôs diferentes alternativas para a sintaxe de caso de mudança na PEP 3103, mas todos eles pareciam ter alguns problemas e a ideia não ganhou apoio popular suficiente. Por conseguinte, a proposta foi rejeitada. Python versão 3.10 muda isso.
A correspondência de padrões é o equivalente de switch em Python 3.10 e mais novo. Uma instrução de correspondência de padrões começa com a palavra-chave de correspondência, e contém um ou mais blocos de caixa.
Atualmente, o Python 3.10 só está disponível como uma versão alfa.
Levará muito tempo até que qualquer código de biblioteca possa adaptar com segurança essa nova funcionalidade, já que o Python 3.9 chegará ao seu fim de vida aproximadamente em outubro de 2025. Portanto, dividi este post em duas partes, dependendo de qual versão você precisa segmentar. Na primeira parte vamos passar pelo novo modelo de correspondência de padrões, então vou mostrar como emular o switch-case em versões mais antigas. Role para baixo se você só estiver interessado no método antigo.
Caso de switch em outros idiomas
Antes de ir para os detalhes do Python, vamos começar com outro idioma que implementa a instrução do caso do switch, C++. A sintaxe é simples, você inicia um bloco com , definir condições com , e sair das condições com , por exemplo:switchcasebreak
case 1: cout << "First case";
case 2: cout << "Second case";
default: cout << "Didn't match a case";
O código acima usa o valor de decidir o que deve ser impresso no console. Se não houver correspondência, então o valor padrão será impresso. Se a declaração de quebra for excluída, então a execução continuará na próxima linha.i
Com um valor de entrada de 1 o código acima imprimiria todos os casos. Normalmente você gostaria de ter as declarações de quebra lá, esquecendo-se de adicionar uma pode ser uma fonte para bugs em seu código.
Em seguida, vamos ver como isso se traduz em Python.
Como já mencionei, a partir do Python 3.10 você pode começar a usar um recurso chamado correspondência de padrões estruturais. A especificação completa está disponível no PEP 634, e uma introdução mais amigável pode ser encontrada a partir de PEP 636. Aqui estou tentando te dar uma rápida introdução ao conceito.
O mathcing padrão não é apenas o seu típico caso de troca, mas na verdade é muito mais poderoso do que isso. No entanto, você pode usá-lo como uma instrução de caso de switch também, isso é equivalente ao primeiro exemplo C++:
print("First case")
print("Second case")
print("Didn't match a case")
As principais diferenças para C++ são que a palavra-chave é em vez de , você não precisa usar para impedir que o código continue com o próximo caso, e o caso padrão é definido com um sublinhado, que sempre corresponderá.matchswitchbreak
Sequências correspondentes
Além de combinar padrões literais como literais de string, números literais, valores booleanos ou , você pode criar padrões de captura mais complicados. Seus casos também podem ser listas ou tuplas, permitindo que você corresponda a diferentes combinações.None
Digamos que você precisa fazer solicitações da Web com base em uma entrada onde você obtém o método HTTP, URL de destino e possíveis dados. Sua função precisa suportar, e levantar um erro de outra forma. A implementação com correspondência de padrões e a biblioteca Requests HTTP poderia ser assim (se esquecermos a existência de solicitações.solicitação):GETPOSTDELETE
def do_request(method, url, data=None):
match (method, data):
case ("GET", None):
case ("POST", _):
case ("DELETE", None):
raise ValueError("Invalid arguments")
Os casos são combinados com base nos valores dentro da tupla, de modo que apenas as combinações de argumentos permitidas serão executadas. Se você tentar adicionar dados a outros métodos que, você receberá um .POSTValueError
As sequências também podem variar de comprimento. Esta é uma estrutura falsa para um programa de linha de comando que tem diferentes níveis de informações de ajuda e dois comandos:
print("Print general help for all available options")
case ["help", "start"]:
print("Print detailed help for the start command")
case ["help", "stop"]:
print("Print detailed help for the stop command")
case ["help", command]:
print(f"No help entry for command: {command}")
case ["start", *args]:
print(f"Run start command with: {args}")
case ["stop", *args]:
print(f"Run stop command with: {args}")
print("No command defined!")
A variável contém os argumentos da linha de comando para o programa, e o primeiro valor é sempre o nome do script em execução, por isso usamos o fatiamento da lista para deixar isso de lado e usar apenas os argumentos restantes.sys.argv
Se o argumento de entrada for , então o programa imprimirá um texto de ajuda geral que poderia mostrar uso básico e opções disponíveis. Em seguida, a digitação mostraria informações mais detalhadas sobre a funcionalidade inicial, e assim por diante.helphelp start
O quarto caso captura ajuda com um comando desconhecido. Isso corresponde a uma sequência de dois elementos onde o primeiro item é a sequência , e o segundo item é salvo em uma variável chamada . Isso realmente mostra o poder da correspondência de padrões, uma vez que você também pode combinar com a estrutura da entrada, não apenas valores."help"command
Os casos mostram como combinar uma sequência de itens usando o operador. Com a desempacotamento de sequência, você pode combinar zero ou mais itens em qualquer posição da sequência. Apenas um item pode usar uma estrela no padrão.startstop*
Ou padrões
Se ambos e chamados a mesma função, não faria muito sentido separá-los a casos diferentes. Os padrões de casos podem ser usados como operador de OR para corresponder a muitos valores diferentes. A função start and stop pode ser refatorada para isso:startstop|
case [("start" | "stop") as command, *args]:
print(f"Run {command} command with: {args}")
Aqui o primeiro item da lista corresponde a ou , e então o resto do comando é capturado como uma sequência. A palavra-chave armazena o valor real compatível como a variável para que possamos usá-lo mais tarde."start""stop"ascommand
Esse padrão tem uma restrição: as alternativas devem vincular as mesmas variáveis. Então você não pode ter um lado vincular um valor a , e, em seguida, tentar usar do outro lado como este:xy
case ["start", x] | ["stop", y]:
Objetos correspondentes
O matcher não se limita a tipos e cordas primitivas, mas também funciona com objetos. Considere esta classe simples que poderia ser usada em um serviço web:User
Você pode querer lidar com usuários administrativos de uma maneira diferente dos usuários não administradores. Além disso, talvez os residentes da UE precisem de algum tratamento especial:
case User(admin=True):
print("This is an admin user")
case User(region="EU"):
print("This is a customer in the EU")
Mesmo que os casos pareçam que estão criando objetos usando construtores, não é o caso. A correspondência de padrões usa essas definições de construtor para encontrar a correspondência correta, qualquer atributo que é deixado indefinido será tratado como um curinga.
Portanto, o primeiro caso será compatível com todos os usuários que são administradores. O segundo caso se aplica a usuários cuja região é , e o terceiro corresponde a todos os usuários restantes. O último caso é usado quando o objeto não é um usuário, mas, por exemplo."EU"None
Big gotcha com constantes!
Até agora eu tenho usado valores literais nas declarações correspondentes, mas não seria melhor defini-los como constantes? É aqui que você precisa ter cuidado para não introduzir bugs em seu código!
Se continuarmos com a classe da seção anterior, poderíamos pensar que é mais limpo lidar com o caso especial da UE com uma constante para que não precisemos usar valores codificados em todos os lugares:User
REGION_EU = "EU"
case User(region=REGION_EU):
print(f"This is a customer in {REGION_EU}")
Aqui chegamos ao ponto de capturar valores variáveis. Agora, você esperaria que um usuário que está na América do Norte () não correspondesse, e o código diria que o cliente está em outro lugar. O que realmente acontece é que a variável REGION_EU agora conterá a string "NA" e o caso vai coincidir!"NA"
Isso significa que podemos capturar variáveis também de objetos. Então, se só estivermos interessados na região de usuários não administrativos, poderíamos usar esse tipo de padrão:
Mas como podemos usar constantes, então? Eles precisam ser nomes pontilhados para evitar que o matcher sobrepondo os valores. Uma opção é usar enumerações:
case User(admin=False, region=Region.EUROPE):
print("European customer")
print("Other customer")
print("Admin user")
Casos com condicional
Agora você pensaria que era tudo e não poderia haver mais nada para lembrar? errado! Você também pode incluir condicionals nos casos.
Vamos continuar com o exemplo do usuário um pouco mais adiante. Desta vez, queremos lidar com o caso em que dois usuários estão na mesma região:
case [User() as u1, User() as u2] if u1.region == u2.region:
print(f"Received two users in the same region: {u1.region}")
case [User(), User()]:
print(f"Received two users in different regions")
Podemos capturar itens nos padrões com a palavra-chave. Com o condicional, o padrão só corresponderá totalmente se a expressão condicional também coincidir. Se a peça antes dos jogos, as variáveis serão iniciadas antes de avaliar a condição. Eles permanecerão disponíveis em etapas posteriores também, então esteja ciente de que isso tem o potencial de escrever coisas, mesmo que a condição em si não corresponda totalmente devido à declaração if.asififu1u2
Se a sua versão Python não suportar a correspondência de padrões, então você precisa usar um método alternativo para imitar o estojo de switch. Há basicamente duas maneiras de fazer isso, então vamos ver o que eles são.
Sim, Elif, senão.
A maneira mais básica e fácil de emular uma instrução de switch é usar declarações simples se mais. O equivalente ao primeiro exemplo C++ seria assim:
def print_case(value):
O é comparado com todas as opções até que uma partida seja encontrada ou até que a declaração final seja alcançada.value
O código é bastante fácil de argumentar, mas com muitos casos pode se tornar difícil de gerenciar. Há também muitas comparações de estado que precisamos escrever manualmente, que podem ser propensas a erros. No entanto, esta provavelmente deve ser sua escolha para casos simples com apenas alguns estados possíveis. Isso também é o que os documentos Python sugerem como substituto.
Outra maneira de alcançar resultados semelhantes é colocar callables dentro de um dicionário. Este método também é mencionado nos documentos Python.
1: lambda: print("First case"),
2: lambda: print("Second case"),
3: lambda: print("Third case"),
4: lambda: print("Fourth case"),
cases.get(value, lambda: print("Didn't match a case"))()
Como você pode ver, isso pode parecer um pouco mais limpo com mais opções em comparação com as declarações if-else.
Neste código cada tecla do dicionário contém uma função lambda sem parâmetros, que então chama a função de impressão apropriada. As funções lambda são funções anônimas que podem ser armazenadas em variáveis e, em seguida, usadas como funções normais.
A função correta é buscada usando o método do dict e, em seguida, é chamado sem argumentos. O segundo argumento do método get é o valor padrão que imprimirá uma mensagem se um caso correspondente não for encontrado.get
Observe os parênteses no final da última linha. Essa é a chamada de função real.
Se você não quer ter uma ação padrão, então isso também funcionaria:
cases[value]()
Se você tentar chamar a função com um valor inválido, como o código, levantará um . Isso pode ser desejado para que seu programa não continue com uma entrada inválida. Apenas certifique-se de lidar com o erro em outro lugar do seu código.print_case5KeyError
E se você precisar adicionar parâmetros aos casos?
Vamos mudar um pouco nosso exemplo. E se quiséssemos criar uma função chamada que pegasse um operador e dois operandos, e devolvesse o resultado do cálculo? Poderíamos implementá-lo assim:calculate
def calculate(operator, x, y):
return cases[operator](x, y)
Cada uma das funções lambda agora tem dois argumentos que são definidos antes do cólon. A chamada de função final passa então os argumentos e para a lambda selecionada. Observe que as funções lambda não contêm a palavra-chave.xyreturn
Aqui estão algumas chamadas de amostra no shell interativo.
>>> calculate("+", 50, 21)
>>> calculate("-", 50, 21)
>>> calculate("*", 50, 21)
>>> calculate("/", 50, 21)
Na minha opinião, esta solução parece bastante limpa em comparação com a opção if-else.
Lembre-se que você não precisa usar funções lambda em tudo:
Os resultados ainda são os mesmos. Desta vez, usamos funções normais nomeadas que são usadas como objetos no diferencial de procuração do caso.
Se você está realmente preocupado com o desempenho, então você pode mover a geração de dicionário para fora da função. Então não será regenerado cada vez que for chamado. Na prática, não deve haver muita diferença.calculate
A correspondência de padrões é uma besta totalmente nova para enfrentar, e pode ser um pouco intimidante no início. Espero que este post tenha lançado alguma luz sobre o tema, e você se sente confortável para aprender mais.