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

switch(i) {
    case 1: cout << "First case";
    break;
    case 2: cout << "Second case";
    break;
    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

switch(i) {
    case 1: cout << "First case";
    case 2: cout << "Second case";
    default: cout << "Didn't match a case";
}

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.

Correspondência de padrões

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++:

match i:
    case 1:
        print("First case")
    case 2:
        print("Second case")
    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

import requests

def do_request(method, url, data=None):
    match (method, data):
        case ("GET", None):
            return requests.get(url)
        case ("POST", _):
            return requests.post(url, data=data)
        case ("DELETE", None):
            return requests.delete(url)
        case _:
            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:

import sys

main():
    match sys.argv[1:]:
        case ["help"]:
            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}")
        case []:
            print("No command defined!")
        case _:
            print("Invalid input")

if __name__ == "__main__":
    main()

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|

match sys.argv[1:]:
    ...
    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

from dataclasses import dataclass

@dataclass
class User:
    id: int
    username: str
    email: str
    admin: bool
    region: str

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:

match user:
    case User(admin=True):
        print("This is an admin user")
    case User(region="EU"):
        print("This is a customer in the EU")
    case User():
        print("This is a customer somewhere else")
    case _:
        print("Error: Not a user!")

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"

match user:
    ...
    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:

match user:
    case User(admin=False, region=region):
        print(f"This is a customer in {region}")
    case User():
        print("This is an admin user")
    case _:
        print("Error: Not a user!")

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:

from dataclasses import dataclass
from enum import Enum

class Region(str, Enum):
    AFRICA = "AF"
    ASIA = "AS"
    EUROPE = "EU"
    NORTH_AMERICA = "NA"
    OCEANIA = "OC"
    SOUTH_AND_CENTRAL_AMERICA = "SA"

@dataclass
class User:
    id: int
    username: str
    email: str
    admin: bool
    region: Region

...

match user:
    case User(admin=False, region=Region.EUROPE):
        print("European customer")
    case User(admin=False):
        print("Other customer")
    case User(admin=True):
        print("Admin user")
    case _:
        print("Error: Not a 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:

match users:
    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")
    case _:
        print("Received something else")

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

Métodos alternativos

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):
    if value == 1:
        print("First case")
    elif value == 2:
        print("Second case")
    else:
        print("Didn't match a case")

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.

Dicionário

Outra maneira de alcançar resultados semelhantes é colocar callables dentro de um dicionário. Este método também é mencionado nos documentos Python.

def print_case(value):
    cases = {
        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:

def print_case(value):
    cases = {
        1: lambda: print("First case"),
        2: lambda: print("Second case"),
        3: lambda: print("Third case"),
        4: lambda: print("Fourth case"),
    }
    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

Um exemplo mais complexo

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):
    cases = {
        "+": lambda a, b: a + b,
        "-": lambda a, b: a - b,
        "*": lambda a, b: a * b,
        "/": lambda a, b: a / b,
    }
    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)
71
>>> calculate("-", 50, 21)
29
>>> calculate("*", 50, 21)
1050
>>> calculate("/", 50, 21)
2.380952380952381

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:

def add(x, y):
    return x + y

def subtract(x, y):
    return x - y

def multiply(x, y):
    return x * y

def divide(x, y):
    return x / y

def calculate(operator, x, y):
    cases = {
        "+": add,
        "-": subtract,
        "*": multiply,
        "/": divide,
    }
    return cases[operator](x, y)

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

Conclusão

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.