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?".
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
Versão Community grátis · Windows
Faixa ou valor exato
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 deelif, e a ordem das faixas importa, como no artigo anterior. - Se as condições são só
==contra o mesmo valor repetido, pense eminou 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
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 nomespergunta se a chave existe, sem buscar o valor. Oinde 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: 7KeyError 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 {}.
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
Plano Essencial grátis para sempre
Trocando a escada pelo 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 syntaxO 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 callableA 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 4Com 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.