Pular para o conteúdo

Fundamentos

Escolha entre muitas opções em Python: elif, in e dicionários

Saiba quando usar uma cadeia de elif, o operador in ou um dicionário para escolher entre muitas opções em Python, e por que o MicroPython não tem match/case.

Por Prof. Rodrigo Faustino · Atualizado em · 14 min de leitura

Para escolher entre muitas opções em Python há três ferramentas, e a melhor depende do que a condição compara. Quando o programa decide por faixas de valores, como uma nota ou uma temperatura, a cadeia de elif é a ferramenta certa. Quando ele compara com dois ou três valores exatos, o operador in encurta a condição. Quando precisa traduzir muitos valores exatos em respostas, como um mês em nome ou um código de erro em mensagem, o dicionário faz o mesmo trabalho com menos linhas e deixa os dados num lugar só. O MicroPython não tem match e case, então essa é a lista completa de caminhos na ESP32.

O artigo anterior mostrou o elif para faixas. Este mostra o que fazer quando a escada fica comprida demais, apresenta o dicionário no tamanho de que a decisão precisa (a série tem um artigo inteiro sobre ele mais adiante) e explica por que um código copiado da internet com match não roda na placa.

Uma escada que só cresce

A calculadora com menu é o exemplo clássico: o usuário escolhe uma operação, e o programa faz a conta.

for op in ("+", "-", "*", "/", "%", "?"):
    a = 12
    b = 5
    if op == "+":
        r = a + b
    elif op == "-":
        r = a - b
    elif op == "*":
        r = a * b
    elif op == "/":
        r = a / b
    else:
        r = "operacao invalida"
    print(a, op, b, "=", r)

A saída mostra 12 + 5 = 17, 12 - 5 = 7, 12 * 5 = 60, 12 / 5 = 2.4 e, para as duas últimas linhas, 12 % 5 = operacao invalida e 12 ? 5 = operacao invalida. O programa funciona, e o else final cumpriu o papel que o artigo anterior pediu dele: o ? não existe, e o % ainda não foi programado. Repare no detalhe do %: ele é uma operação válida em Python, mas a escada não tem o degrau dele, então a mensagem diz "inválida" para uma conta que o programa poderia fazer. Cada operação nova pede mais duas linhas, e nenhuma conta de cabeça garante que as comparações estejam todas escritas certas.

Aqui a escada ainda é pequena. O desconforto aparece quando ela chega a doze degraus, como num programa que converte o número do mês em nome, e a decisão em cada degrau é a mesma: "o valor é igual a este?".

FastTeacherPara professores

Da ementa ao semestre planejado

  • Plano de ensino e calendário montados sozinhos
  • Diário de aulas pronto para colar no SIGA
  • Slides e listas de exercícios com gabarito
Conhecer o FastTeacher

Versão Community grátis · Windows

Faixa ou valor exato

Um fluxograma que parte de olhar a condição e abre três caminhos. Faixas de valores, como nota maior ou igual a 9, levam ao elif. Dois ou três valores exatos, como x igual a 1, 2 ou 3, levam a elif ou ao operador in. Muitos valores exatos, como um código que vira texto, levam ao dicionário. Uma faixa final resume: faixa pede elif, valor exato em muitos casos pede dicionário.
Para escolher a ferramenta, olhe o que a condição compara: faixas, poucos valores exatos ou muitos valores exatos.

A pergunta que organiza a escolha é o que a condição compara. Uma nota de 0 a 10 tem infinitos valores possíveis (7, 7.5, 7.0001), e a decisão trata cada faixa como um bloco. Nenhum dicionário ajuda aí, porque seria preciso listar todos os valores. Um mês, por outro lado, só pode ser 1, 2, 3 até 12, e cada valor tem a sua resposta.

  • Se as condições usam >, >=, < ou <=, use a cadeia de elif, e a ordem das faixas importa, como no artigo anterior.
  • Se as condições são só == contra o mesmo valor repetido, pense em in ou num dicionário.

Um dicionário com faixas até parece funcionar, e é a armadilha que os alunos encontram. Para ver, considere um dicionário em que a nota 7 vale "B":

nota = 7
niveis = {10: "A", 9: "A", 8: "B", 7: "B"}
print(niveis.get(nota, "?"))
print(niveis.get(7.5, "?"))

A saída é B e ?. A nota 7 está na lista, mas a 7.5 não, porque o dicionário só conhece os valores que foram escritos nele. Para faixas, elif.

O operador in: vários valores, uma pergunta

Quando a mesma resposta vale para vários valores, a condição com or repete a variável em cada comparação:

x = 3
if x == 1 or x == 2 or x == 3:
    print("dentro")
if x in (1, 2, 3):
    print("dentro com in")
if x in [1, 2, 3]:
    print("dentro com lista")
c = "b"
if c in "abc":
    print("b esta em abc")
print("bc" in "abc", "ac" in "abc")

A saída tem dentro, dentro com in, dentro com lista, b esta em abc e, na última linha, True False. A pergunta x in (1, 2, 3) é "o valor de x aparece nesta sequência?", e os parênteses (uma tupla) e os colchetes (uma lista) servem igualmente aqui. Com texto, in pergunta se um pedaço está dentro do outro: "bc" in "abc" é verdadeiro porque bc aparece em sequência, e "ac" in "abc" é falso porque as duas letras não ficam coladas.

O erro mais clássico com vários valores é escrever o or só com a segunda metade:

op = "%"
if op == "+" or "-":
    print("entrou")
if op in ("+", "-"):
    print("nao entra")
else:
    print("else correto")

A saída é entrou e else correto. Já foi o assunto do artigo sobre operadores lógicos: a parte "-" sozinha é um texto não vazio, que o Python trata como verdadeiro, e o or deixa toda a condição passar. O in corrige o erro de graça, porque só tem um lado a escrever.

O dicionário: chave e valor

À esquerda, o dicionário meses com três pares: a chave 1 aponta para janeiro, a chave 2 para fevereiro e a chave 3 para março. À direita, quatro consultas: meses[3] devolve março, meses.get(7, invalido) devolve invalido, 7 in meses devolve False e meses[7] causa KeyError: 7. Uma faixa avisa para usar get com valor padrão ou perguntar antes com in.
Um dicionário liga chaves a valores e tem três formas de consulta: colchetes, get e in.

Um dicionário é um conjunto de pares. Cada par liga uma chave a um valor, e a consulta pela chave devolve o valor. Escreve-se entre chaves, com os dois-pontos entre cada chave e o valor, e vírgulas entre os pares:

nomes = {1: "janeiro", 2: "fevereiro", 3: "marco", 12: "dezembro"}
for m in (1, 3, 12, 7):
    print(m, nomes.get(m, "mes invalido"))
print(nomes[3])
print(3 in nomes, 7 in nomes)
print(len(nomes))

A saída é 1 janeiro, 3 marco, 12 dezembro, 7 mes invalido, depois marco, depois True False e 4. Há quatro formas de usar o dicionário nesse trecho:

  • nomes[3] busca a chave 3 e devolve "marco".
  • nomes.get(7, "mes invalido") busca a chave 7, e como ela não existe devolve o segundo argumento, o valor padrão.
  • 3 in nomes pergunta se a chave existe, sem buscar o valor. O in de um dicionário olha as chaves, nunca os valores.
  • len(nomes) conta os pares.

Os colchetes têm um risco que o get não tem. Se a chave não existe, o programa para:

nomes = {1: "janeiro", 2: "fevereiro"}
print(nomes[7])
Traceback (most recent call last):
  File "main.py", line 2, in <module>
KeyError: 7

KeyError quer dizer "chave não encontrada", e o número que vem depois dos dois-pontos é a chave que faltou. A regra prática: use get com um valor padrão quando a ausência da chave for um caso normal (um código desconhecido, um mês digitado errado), e colchetes quando a ausência for um defeito do programa que merece parar com um erro claro.

Um dicionário também muda. Dá para acrescentar um par, trocar o valor de uma chave e apagar um par:

pinos = {"led": 2, "botao": 4, "buzzer": 5}
print(pinos["led"])
pinos["rele"] = 15
print(pinos["rele"], len(pinos))
pinos["led"] = 13
print(pinos["led"])
del pinos["buzzer"]
print("buzzer" in pinos)
print({}, len({}))

A saída é 2, 15 4, 13, False e {} 0. A atribuição pinos["rele"] = 15 cria o par quando a chave é nova e troca o valor quando a chave já existe. Um dicionário vazio se escreve {}.

FastMindAssistente de IA local

A IA que lembra da sua vida e mora no seu computador

  • Memória de longo prazo com a fonte de cada resposta
  • Funciona offline: seus dados ficam no seu PC
  • Lê PDFs, slides, fotos e gravações de reunião
Baixar grátis para Windows

Plano Essencial grátis para sempre

Trocando a escada pelo dicionário

Dois quadros de código lado a lado que traduzem códigos de resposta em mensagens. À esquerda, uma cadeia de if, elif e else com quatro comparações em dez linhas. À direita, um dicionário mensagens com quatro pares e uma única linha com get e valor padrão. Uma faixa informa que as saídas são iguais no simulador: 200 ok, 404 nao encontrado e 503 desconhecido.
A mesma tradução de código em mensagem, escrita como cadeia de elif e como dicionário.

Os códigos de resposta de um servidor web, como o 404 de "página não encontrada", são o exemplo perfeito: cada número tem uma mensagem. Aqui estão as duas versões, uma depois da outra, com a mesma entrada:

for c in (200, 301, 404, 500, 503, 418):
    if c == 200:
        msg = "ok"
    elif c == 301:
        msg = "movido"
    elif c == 404:
        msg = "nao encontrado"
    elif c == 500:
        msg = "erro no servidor"
    else:
        msg = "desconhecido"
    print(c, msg)
for c in (200, 404, 503):
    print(c, {200: "ok", 404: "nao encontrado"}.get(c, "desconhecido"))

A primeira parte imprime 200 ok, 301 movido, 404 nao encontrado, 500 erro no servidor, 503 desconhecido e 418 desconhecido. A segunda, com um dicionário menor para caber numa linha, imprime 200 ok, 404 nao encontrado e 503 desconhecido. O resultado é o mesmo para os códigos que os dois conhecem.

Na prática, o dicionário vai para uma variável com nome, como mensagens, e a lógica fica numa linha só: msg = mensagens.get(c, "desconhecido"). Acrescentar um código novo é acrescentar um par. Quem lê o programa vê os dados num bloco e a regra numa linha, e quem quer corrigir uma mensagem edita uma linha de dados, sem se preocupar com os : e o recuo dos blocos. Esse é o ganho principal. O dicionário não faz o programa mais rápido de um jeito que alguém note numa ESP32 com poucas opções, e nem precisa: a vantagem é de organização.

Chaves precisam ser do mesmo tipo

Num dicionário com a chave 404 (um int), a busca por "404" (um texto) não acha nada, porque os dois valores são diferentes:

mensagens = {200: "ok", 404: "nao encontrado"}
print(mensagens.get("404", "desconhecido"))
print(mensagens.get(int("404"), "desconhecido"))
print(404 == "404")

A saída é desconhecido, nao encontrado e False. É a armadilha do input() de novo: o que o usuário digita é sempre texto, e a chave 404 só aparece quando o programa converte. O artigo sobre conversão de tipos tem a causa, e a defesa aqui é a mesma: converta com int() antes de buscar, ou escreva as chaves do dicionário como texto desde o início.

Maiúsculas e espaços

Quando a chave é um texto digitado, "SEG" e "seg" são chaves diferentes. A defesa é normalizar o que o usuário digitou antes de consultar:

dias = {"seg": "segunda", "ter": "terca"}
for entrada in ("seg", " SEG ", "Seg", "xyz"):
    c = entrada.strip().lower()
    print(repr(entrada), repr(c), dias.get(c, "?"))

A saída mostra que 'seg', ' SEG ' e 'Seg' viram a chave 'seg' e resultam em segunda, e o 'xyz' fica em ?. O strip() tira os espaços das pontas, o lower() passa tudo para minúsculas, e a função repr() imprime o texto com aspas para os espaços ficarem visíveis.

A ordem de um dicionário

Nenhum exemplo até aqui depende da ordem dos pares, e isso não é acaso. Percorrer um dicionário com for entrega as chaves numa ordem que o programa não escolhe:

d = {"x": 1, "y": 2}
print(list(d))
print(sorted(d))
for chave in sorted(d):
    print(chave, d[chave])

No simulador, a saída é ['y', 'x'] para list(d), depois ['x', 'y'] para sorted(d) e então x 1 e y 2. O Python do computador mostraria ['x', 'y'] já na primeira linha, porque o CPython guarda os pares na ordem em que entraram. O MicroPython não promete essa ordem, então um programa que precisa de uma sequência previsível deve pedi-la com sorted(), que devolve as chaves em ordem alfabética ou numérica.

Um aviso a mais: se a mesma chave aparece duas vezes na hora de escrever o dicionário, o Python não reclama. Em {"a": 1, "a": 2, "b": 3}, o a vale 2, e o dicionário tem dois pares. Ao copiar e colar uma linha, vale conferir se a chave foi trocada.

Por que o MicroPython não tem match/case

O Python 3.10, de 2021, ganhou uma estrutura nova para escolher entre muitos valores, o match com vários case. Quem pesquisa "switch em Python" encontra exemplos assim:

codigo = 404
match codigo:
    case 200:
        print("ok")
    case 404:
        print("nao encontrado")
    case _:
        print("outro")

No Python do computador, a saída é nao encontrado. No simulador e na ESP32 o resultado é outro:

Traceback (most recent call last):
  File "main.py", line 2
SyntaxError: invalid syntax

O MicroPython implementa o Python 3.4 e uma seleção de recursos de versões mais novas, e o match ainda não está entre eles. A página de diferenças para o Python 3.10, na documentação oficial do MicroPython, lista o PEP 634, que define o match, como assunto em discussão. O programa, então, nem começa: o erro aparece na compilação, antes da primeira linha rodar. Isso pega alunos que copiam um tutorial recente, e vale reconhecer o sintoma: um SyntaxError logo na linha do match.

As substituições são as que este artigo apresentou. Para valores exatos, o dicionário (ou a cadeia de elif com ==); para vários valores na mesma resposta, o in; para faixas, o elif. O case _ do match, que pega o que sobrou, é o else da cadeia ou o valor padrão do get. Nenhuma dessas trocas perde nada em relação ao match para os usos de um programa de placa.

O que muda na ESP32

O dicionário funciona do mesmo jeito no MicroPython, e dois usos aparecem muito em projetos de hardware. O primeiro é dar nome aos pinos, que facilita a leitura do programa:

from machine import Pin
pinos = {"led": 2, "rele": 15}
for nome in ("led", "rele", "motor"):
    if nome in pinos:
        saida = Pin(pinos[nome], Pin.OUT)
        saida.value(1)
        print(nome, "ligado no pino", pinos[nome], saida.value())
    else:
        print(nome, "nao existe")

A saída é led ligado no pino 2 1, rele ligado no pino 15 1 e motor nao existe. O in antes da busca evita o KeyError para um nome que ninguém cadastrou, e o else diz qual foi o nome desconhecido. O segundo uso é a tradução de códigos de erro de um servidor web, como o exemplo dos códigos de resposta, quando a placa fala com a internet, tema do artigo sobre Wi-Fi, HTTP e MQTT.

Cada par de um dicionário ocupa memória na ESP32, que tem bem menos RAM que um computador. Para dezenas de pares, como os meses, os dias da semana ou os pinos de um projeto, isso não é problema. Uma tabela com milhares de itens é outra conversa, e esse tipo de cálculo costuma aparecer só em projetos maiores.

Em sala de aula: onde a escolha costuma tropeçar

Os casos abaixo se repetem nas turmas que chegam a este assunto. Cada um produz um programa que roda e erra, ou erra de um jeito que não é o que o aluno procura.

O degrau repetido

Ao copiar e colar um bloco do elif, o aluno esquece de trocar o valor, e o programa ganha dois degraus iguais. O Python não reclama, e o segundo nunca é executado:

op = "+"
if op == "+":
    print("soma")
elif op == "-":
    print("subtracao")
elif op == "+":
    print("soma de novo, nunca roda")

A saída é só soma. Cada degrau é testado em ordem, e o primeiro que acerta decide, como no artigo anterior. O defeito aparece quando o aluno acha que o - não funciona e olha o bloco errado. No dicionário, a versão desse erro é a chave repetida, que também passa sem aviso.

O caso esquecido

O % da calculadora, que caiu no else. Uma lista no papel com todos os valores que o programa precisa tratar, antes de digitar, resolve boa parte desses esquecimentos.

O KeyError no primeiro teste

O aluno aprende o dicionário, usa colchetes por todo lado e digita no teste uma chave que não existe. O programa para com KeyError, e o susto é maior que o defeito. A pergunta que ajuda é: "a falta dessa chave é um caso normal?". Se for, get com padrão ou in.

Parênteses no lugar de colchetes

Quem vem do print(...) e de outras funções escreve meses(1) em vez de meses[1]. O Python entende o meses(1) como uma chamada, e como um dicionário não é uma função:

Traceback (most recent call last):
  File "main.py", line 2, in <module>
TypeError: 'dict' object isn't callable

A mensagem do simulador diz isn't callable, e a do Python do computador diz is not callable, com o mesmo sentido. "Callable" é o que se pode chamar com parênteses, e dicionário se consulta com colchetes.

O dicionário para faixas

O aluno gosta tanto do dicionário que tenta usá-lo para a nota, lista os valores de 0 a 10 e descobre que a 7.5 não está lá. O raciocínio certo é a pergunta da figura do início do artigo: faixa pede elif.

O tipo da chave

O input() devolve texto, a chave é número, e a busca nunca acha nada, sem erro e sem aviso, como no exemplo do 404. O mesmo vale para o maiúsculo contra o minúsculo e para o espaço perdido no fim do texto digitado.

Exercício: a lanchonete

Um cardápio tem quatro itens, cada um com um nome e um preço: 1, pão com queijo a 6; 2, suco a 5; 3, café a 4; 4, bolo a 8. O programa recebe uma lista de pedidos, diz o que cada um é, acumula o total e avisa quando o código não existe. Cada valor do dicionário é uma tupla com dois elementos, e a linha nome, preco = cardapio[codigo] separa os dois de uma vez.

cardapio = {1: ("pao com queijo", 6), 2: ("suco", 5), 3: ("cafe", 4), 4: ("bolo", 8)}
total = 0
for codigo in (2, 1, 4, 9, 3):
    if codigo in cardapio:
        nome, preco = cardapio[codigo]
        total = total + preco
        print(codigo, nome, preco)
    else:
        print(codigo, "item inexistente")
print("total", total)

A saída é 2 suco 5, 1 pao com queijo 6, 4 bolo 8, 9 item inexistente, 3 cafe 4 e total 23. O código 9 não existe, e o programa segue adiante sem somar nada e sem parar. Para pedir um único código ao usuário, troque o for por uma leitura:

cardapio = {1: ("pao com queijo", 6), 2: ("suco", 5), 3: ("cafe", 4), 4: ("bolo", 8)}
codigo = int(input("Codigo do item (1 a 4): "))
if codigo in cardapio:
    nome, preco = cardapio[codigo]
    print(nome, "custa", preco)
else:
    print("item inexistente")
Codigo do item (1 a 4): 3
cafe custa 4

Com a entrada 9, a saída é item inexistente. Como o int() converte antes da busca, a chave tem o tipo certo. Se o usuário digitar uma palavra, o int() falha antes de o dicionário entrar em cena, e esse é um problema de conversão, tratado no artigo sobre conversão de tipos.

Perguntas frequentes

Qual é mais rápido, elif ou dicionário?

Com poucas opções, a diferença não aparece numa ESP32. A escolha vale pela clareza: se a condição é uma faixa, elif; se é um valor exato num grupo grande, dicionário. A cadeia de elif compara um degrau por vez, e o dicionário vai direto à chave, o que só faria diferença com centenas de itens.

Posso usar texto como chave?

Pode, e é comum: {"led": 2}. Números, textos e tuplas servem de chave. Uma lista não serve, porque ela pode mudar.

O que o get devolve se eu não passar o segundo argumento?

Devolve None, o valor que representa "nada". É prático para testar depois, com if resultado is None. Se o programa vai imprimir o resultado, passe um texto como padrão.

Posso colocar um dicionário dentro de outro?

Pode. Cada valor pode ser qualquer coisa, inclusive outro dicionário, uma lista ou uma tupla, como no cardápio. O artigo sobre dicionários da série mostra os usos com calma.

Como faço o equivalente do switch de C?

Com a cadeia de elif para poucos casos ou com um dicionário para muitos. Os valores de um dicionário também podem ser funções, o que dá um menu de comandos sem nenhum elif. Esse uso vem com o módulo de funções.

Para continuar

A trilha Lógica com Python tem desafios com muitas opções. A Calculadora com menu pede a escada de operações, o Nível de acesso combina valores e regras, os Meses do ano pedem a tradução de número em nome, e o Código de erro é o caso das mensagens. Se o elif ainda confunde, releia o artigo sobre if, elif e else.

Com isto o módulo de decisão está completo: o programa já escolhe o que fazer. O próximo assunto é fazer o mesmo trecho de código rodar muitas vezes, os laços, que começam pelo motivo de repetir.

Para praticar no PyPlaca

Trilhas

Lições

Fichas de componentes

FastMindAssistente de IA local

A IA que lembra da sua vida e mora no seu computador

  • Memória de longo prazo com a fonte de cada resposta
  • Funciona offline: seus dados ficam no seu PC
  • Lê PDFs, slides, fotos e gravações de reunião
Baixar grátis para Windows

Plano Essencial grátis para sempre

FastTeacherPara professores

Da ementa ao semestre planejado

  • Plano de ensino e calendário montados sozinhos
  • Diário de aulas pronto para colar no SIGA
  • Slides e listas de exercícios com gabarito
Conhecer o FastTeacher

Versão Community grátis · Windows

FastMindAssistente de IA local

A IA que lembra da sua vida e mora no seu computador

  • Memória de longo prazo com a fonte de cada resposta
  • Funciona offline: seus dados ficam no seu PC
  • Lê PDFs, slides, fotos e gravações de reunião
Baixar grátis para Windows

Plano Essencial grátis para sempre