Python Dict e Set Comprehension
Na aula List Comprehension, você viu como montar uma lista numa linha só: [n * 2 for n in numeros]. A mesma ideia vale para dicionários e conjuntos. Com a dict comprehension, a Ana monta a tabela de preços com aumento, o estoque sem os produtos zerados ou o boletim da turma, cada um numa linha. Com a set comprehension, ela monta um conjunto do mesmo jeito.
- A escrever uma dict comprehension:
{chave: valor for item in sequência}. - A transformar os valores de um dicionário com
items(). - A filtrar pares com
ifno fim. - A montar um dicionário a partir de duas listas com
zip(), e a inverter chaves e valores. - A set comprehension:
{x for x in …}. - Os erros reais e a pegadinha que troca o dicionário por um conjunto sem avisar.
A ideia: a mesma receita, outra forma
Pense numa receita que diz: "para cada fruta da cesta, escreva numa etiqueta o nome da fruta e, ao lado, quantas letras ela tem". Essa frase descreve um dicionário inteiro sem você precisar listar cada fruta. A comprehension é essa frase escrita em Python: o que vai na etiqueta (chave: valor), e de onde vêm os itens (for … in …).
| Para montar | Comprehension | Envolve com |
|---|---|---|
| Uma lista | [n * 2 for n in numeros] | Colchetes |
| Um dicionário | {n: n * 2 for n in numeros} | Chaves, com chave: valor |
| Um conjunto | {n * 2 for n in numeros} | Chaves, com um valor só |
1. A primeira dict comprehension
Primeiro, o jeito que você já conhece, com for: um dicionário com cada número de 1 a 5 e o seu quadrado.
quadrados = {}
for n in range(1, 6):
quadrados[n] = n * n
print(quadrados)
$ python3 dc_for.py
{1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
Agora o mesmo resultado, numa linha:
quadrados = {n: n * n for n in range(1, 6)}
print(quadrados)
$ python3 dc_basico.py
{1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
- { … }
- As chaves dizem que o resultado vai ser um dicionário (ou um conjunto; o dois-pontos decide).
- n: n * n
- O par que vai ser criado a cada volta: a chave
n, o dois-pontos, e o valorn * n. É o que, nofor, estava emquadrados[n] = n * n. - for n in range(1, 6)
- De onde vêm os itens: o mesmo
forde sempre, sem os dois-pontos do fim e sem o bloco recuado.
Leia da esquerda para a direita como "o dicionário de n para n * n, para cada n de 1 a 5". A chave e o valor podem vir de qualquer conta sobre o item. Para cada fruta, o tamanho do nome:
frutas = ["manga", "caju", "acerola"]
tamanhos = {fruta: len(fruta) for fruta in frutas}
print(tamanhos)
$ python3 dc_palavras.py
{'manga': 5, 'caju': 4, 'acerola': 7}
2. Transformando um dicionário: items()
O uso mais comum é partir de um dicionário que já existe e criar outro, mudando os valores (ou as chaves). Percorra com items(), que entrega chave e valor (aula Percorrendo um Dicionário):
precos = {"pão": 0.50, "leite": 4.75, "café": 18.90}
com_aumento = {produto: preco + 1 for produto, preco in precos.items()}
print(com_aumento)
em_centavos = {produto: round(preco * 100) for produto, preco in precos.items()}
print(em_centavos)
maiusculas = {produto.upper(): preco for produto, preco in precos.items()}
print(maiusculas)
$ python3 dc_items.py
{'pão': 1.5, 'leite': 5.75, 'café': 19.9}
{'pão': 50, 'leite': 475, 'café': 1890}
{'PÃO': 0.5, 'LEITE': 4.75, 'CAFÉ': 18.9}
- for produto, preco in precos.items()
- A cada volta, um par do dicionário original, já desempacotado.
- produto: preco + 1
- Mesma chave, valor novo: um real a mais.
- produto: round(preco * 100)
- O preço em centavos, arredondado para inteiro.
- produto.upper(): preco
- Agora é a chave que muda: o nome em maiúsculas.
Nos três casos, precos continua igual: a comprehension sempre cria um dicionário novo.
Missão: chegou um caminhão e o estoque de tudo dobrou. Troque o {} por uma dict comprehension que cria dobro com cada quantidade vezes 2.
estoque = {"pão": 40, "leite": 12, "café": 8}
dobro = {}
print(dobro)
estoque = {"pão": 40, "leite": 12, "café": 8}
dobro = {produto: qtd * 2 for produto, qtd in estoque.items()}
print(dobro)
3. Filtrando: o if no fim
Como na list comprehension, um if depois do for escolhe quais itens entram. Só os produtos que ainda têm estoque:
estoque = {"pão": 40, "leite": 0, "café": 8, "bolo": 0}
tem = {produto: qtd for produto, qtd in estoque.items() if qtd > 0}
print(tem)
acabou = [produto for produto, qtd in estoque.items() if qtd == 0]
print(acabou)
$ python3 dc_if.py
{'pão': 40, 'café': 8}
['leite', 'bolo']
A primeira linha cria um dicionário só com os pares em que qtd > 0. A segunda mostra que dá para sair de um dicionário e chegar numa lista: com colchetes, o resultado é uma lista dos nomes que zeraram.
RuntimeError
Na aula Percorrendo um Dicionário, apagar chaves dentro do for deu RuntimeError: dictionary changed size during iteration. A comprehension com if é a outra saída: em vez de apagar do dicionário, você cria um novo só com o que fica, e guarda na mesma variável: estoque = {p: q for p, q in estoque.items() if q > 0}.
if e else no valor
Quando todos os itens entram, mas o valor depende de uma condição, use o if em uma linha (aula if em uma Linha) na posição do valor:
notas = {"Ana": 9, "Bia": 6, "Caio": 10}
situacao = {nome: ("passou" if nota >= 7 else "recuperação") for nome, nota in notas.items()}
print(situacao)
$ python3 dc_if_else.py
{'Ana': 'passou', 'Bia': 'recuperação', 'Caio': 'passou'}
Onde fica o if | O que faz | Exemplo |
|---|---|---|
No fim, depois do for | Filtra: quem não passa no teste não entra | {n: q for n, q in d.items() if q > 0} |
No valor, com else | Escolhe o valor; todos entram | {n: ("sim" if q > 0 else "não") for n, q in d.items()} |
Missão: crie passaram com uma dict comprehension que fica só com os alunos de nota 7 ou mais.
notas = {"Ana": 9, "Bia": 6, "Caio": 10, "Davi": 5}
passaram = notas
print(passaram)
notas = {"Ana": 9, "Bia": 6, "Caio": 10, "Davi": 5}
passaram = {nome: nota for nome, nota in notas.items() if nota >= 7}
print(passaram)
4. Duas listas viram um dicionário
A Ana tem os nomes numa lista e as notas noutra, na mesma ordem. O zip() (aula enumerate() e zip()) anda pelas duas ao mesmo tempo e entrega os pares:
nomes = ["Ana", "Bia", "Caio"]
notas = [9, 6, 10]
boletim = {nome: nota for nome, nota in zip(nomes, notas)}
print(boletim)
print(dict(zip(nomes, notas)))
$ python3 dc_zip.py
{'Ana': 9, 'Bia': 6, 'Caio': 10}
{'Ana': 9, 'Bia': 6, 'Caio': 10}
A segunda linha mostra um atalho: quando você não vai mudar nada nos pares, dict(zip(nomes, notas)) faz o mesmo, mais curto.
Invertendo chaves e valores
Trocando a ordem no par, a chave vira valor e o valor vira chave. A lista de ramais da firma, ao contrário: de quem é o ramal 202?
ramais = {"Ana": 201, "Bia": 202, "Caio": 203}
dono = {ramal: nome for nome, ramal in ramais.items()}
print(dono)
print(dono[202])
$ python3 dc_inverter.py
{201: 'Ana', 202: 'Bia', 203: 'Caio'}
Bia
Isso só funciona bem quando os valores não se repetem. Com valores repetidos, alguém some; veja em Quando dá errado.
5. Set comprehension
Tire a parte chave: e fique com um valor só: o resultado é um conjunto, e as repetições somem, como em todo conjunto.
restos = {n % 3 for n in range(10)}
print(restos)
palavras = ["sol", "mar", "céu", "areia", "onda"]
tamanhos = {len(p) for p in palavras}
print(tamanhos)
nomes = ["Ana", "Bia", "Caio", "Bruno", "Alice"]
iniciais = {nome[0] for nome in nomes}
print(sorted(iniciais))
$ python3 dc_set.py
{0, 1, 2}
{3, 4, 5}
['A', 'B', 'C']
- {n % 3 for n in range(10)}
- O resto da divisão por 3 de cada número de 0 a 9. São dez contas, mas só três resultados diferentes: 0, 1 e 2.
- {len(p) for p in palavras}
- Os tamanhos diferentes das palavras:
sol,marecéutêm 3 letras, e o 3 aparece uma vez. - {nome[0] for nome in nomes}
- As iniciais diferentes. Como é um conjunto de textos, a ordem do
printmudaria a cada execução; osorted()põe em ordem alfabética.
Não existe "tuple comprehension". (n * n for n in range(4)) cria outra coisa, um gerador (aula Geradores), que produz os valores um de cada vez. Para uma tupla, passe o gerador para tuple():
gen = (n * n for n in range(4)) print(type(gen)) print(tuple(n * n for n in range(4)))
$ python3 dc_tupla.py <class 'generator'> (0, 1, 4, 9)
Formas diferentes de fazer a mesma coisa
| Objetivo | Com for | Com comprehension |
|---|---|---|
| Dicionário a partir de uma lista | d = {}for f in frutas: d[f] = len(f) | d = {f: len(f) for f in frutas} |
| Filtrar um dicionário | novo = {}for k, v in d.items(): if v > 0: novo[k] = v | novo = {k: v for k, v in d.items() if v > 0} |
| Duas listas em um dicionário | um for com zip | dict(zip(nomes, notas)) |
| Conjunto a partir de uma lista | s = set()for p in palavras: s.add(len(p)) | s = {len(p) for p in palavras} |
Qual usar? A comprehension quando a regra cabe numa linha que se lê com facilidade: transformar, filtrar, montar. O for comum quando há várias contas por item, print no meio, ou mais de um if/else: uma comprehension enorme é mais difícil de entender do que um laço de quatro linhas.
Quando dá errado
precos = {"pão": 0.50, "leite": 4.75}
copia = {produto, preco for produto, preco in precos.items()}
$ python3 dc_virgula.py
File "/home/ana/dc_virgula.py", line 2
copia = {produto, preco for produto, preco in precos.items()}
^^^^^^^^^^^^^^
SyntaxError: did you forget parentheses around the comprehension target?
- File "/home/ana/dc_virgula.py", line 2
- Sem a palavra
Traceback: é um erro de escrita, achado antes de o programa rodar. - ^^^^^^^^^^^^^^
- Marca
produto, preco: o "alvo" da comprehension, o que ela monta a cada volta. - SyntaxError: did you forget parentheses around the comprehension target?
- "Você esqueceu os parênteses em volta do alvo da comprehension?". Com a vírgula, o Python acha que você queria um conjunto de tuplas, que se escreve
{(produto, preco) for …}, e sugere os parênteses.
Como consertar: para um dicionário, o que separa chave e valor é o dois-pontos: {produto: preco for …}.
for não criou
precos = {"pão": 0.50, "leite": 4.75}
copia = {produto: preco for produto in precos}
$ python3 dc_nameerror.py
Traceback (most recent call last):
File "/home/ana/dc_nameerror.py", line 2, in <module>
copia = {produto: preco for produto in precos}
^^^^^
NameError: name 'preco' is not defined. Did you mean: 'precos'?
O for só criou produto (percorrendo as chaves); preco não existe. O Python ainda sugere precos, que é parecido, mas não é o que se quer. Como consertar: percorra os pares, for produto, preco in precos.items(), ou leia o valor pela chave: {produto: precos[produto] for produto in precos}.
items()
precos = {"pão": 0.50, "leite": 4.75}
copia = {produto: preco for produto, preco in precos}
$ python3 dc_sem_items.py
Traceback (most recent call last):
File "/home/ana/dc_sem_items.py", line 2, in <module>
copia = {produto: preco for produto, preco in precos}
^^^^^^^^^^^^^^
ValueError: too many values to unpack (expected 2)
O mesmo erro da aula sobre percorrer: sem items(), o for entrega só a chave "pão", e o Python tenta desempacotar suas 3 letras em 2 variáveis. Como consertar: precos.items().
precos = {"pão": 0.50, "leite": 4.75}
a = {produto for produto, preco in precos.items()}
b = {produto: preco for produto, preco in precos.items()}
print(type(a))
print(type(b))
$ python3 dc_set_vs_dict.py <class 'set'> <class 'dict'>
Sem o : preco, a linha continua válida, mas monta um conjunto só com os nomes. Nenhum erro aparece ali; ele só vai aparecer depois, quando você tentar ler a["pão"]. Como perceber: o que decide entre dicionário e conjunto é o dois-pontos. Na dúvida, print(type(…)).
notas = {"Ana": 9, "Bia": 6, "Caio": 9}
quem_tirou = {nota: nome for nome, nota in notas.items()}
print(quem_tirou)
$ python3 dc_inverter_perde.py
{9: 'Caio', 6: 'Bia'}
A Ana sumiu. Ela e o Caio tiraram 9, e a chave 9 só pode existir uma vez: primeiro recebeu 'Ana', depois foi trocada por 'Caio', sem aviso. É a regra de sempre: chave repetida, vale a última. Como evitar: só inverta quando os valores forem únicos (ramais, matrículas). Para agrupar quem tirou cada nota, o valor precisa ser uma lista, montada com um for comum.
Referência rápida
| Escrita | O que faz |
|---|---|
{x: f(x) for x in seq} | Dicionário: chave x, valor f(x) |
{k: g(v) for k, v in d.items()} | Dicionário novo com os valores transformados |
{k: v for k, v in d.items() if teste} | Dicionário novo só com os pares que passam no teste |
{k: (a if teste else b) for k, v in d.items()} | Escolhe o valor conforme o teste |
{k: v for k, v in zip(chaves, valores)} / dict(zip(…)) | Dicionário a partir de duas listas |
{v: k for k, v in d.items()} | Inverte chaves e valores (os valores precisam ser únicos) |
{f(x) for x in seq} | Conjunto (sem repetidos) |
tuple(f(x) for x in seq) | Tupla (com parênteses sozinhos é um gerador) |
| Mensagem | Causa provável |
|---|---|
SyntaxError: did you forget parentheses around the comprehension target? | Vírgula no lugar do dois-pontos entre chave e valor |
NameError: name 'preco' is not defined | Usou uma variável que o for da comprehension não cria |
ValueError: too many values to unpack (expected 2) | Faltou o .items() |
<class 'set'> onde esperava dict | Faltou o chave: |
| Um item sumiu, sem erro | Duas voltas geraram a mesma chave: vale a última |
Exercícios
1. O símbolo que faz o dicionário
Complete o que separa a chave do valor:
2. O resultado
Escreva, como o Python mostra, o resultado de {n: n + 1 for n in range(3)}:
3. O filtro
Complete a palavra que fica só com os pares de valor maior que 0:
4. Qual o tipo de {len(p) for p in ["sol", "mar"]}?
5. {v: k for k, v in {"a": 1, "b": 1}.items()} dá o quê?
6. Para montar um dicionário a partir de nomes e notas, duas listas na mesma ordem, o jeito mais curto é:
Missão final: os nomes da turma
Missão: com a lista de nomes (repare que a Ana aparece duas vezes), crie com comprehensions: tamanhos, um dicionário de cada nome para o tamanho dele, e iniciais, o conjunto das primeiras letras. O print das iniciais já usa sorted() para sair sempre igual.
nomes = ["Ana", "Bia", "Caio", "Ana", "Bruno"]
tamanhos = {}
iniciais = set()
print(tamanhos)
print(sorted(iniciais))
nomes = ["Ana", "Bia", "Caio", "Ana", "Bruno"]
tamanhos = {nome: len(nome) for nome in nomes}
iniciais = {nome[0] for nome in nomes}
print(tamanhos)
print(sorted(iniciais))
A Ana repetida não aparece duas vezes no dicionário (a chave é única) nem conta duas vezes o "A" no conjunto.
Resumo
{chave: valor for item in sequência}monta um dicionário numa linha;{valor for item in sequência}monta um conjunto. O dois-pontos decide qual.- Com
for k, v in d.items(), transforme as chaves ou os valores de um dicionário que já existe; o original não muda. - Um
ifno fim filtra; umif … else …no valor escolhe o valor. dict(zip(chaves, valores))junta duas listas;{v: k for k, v in d.items()}inverte (com valores únicos).- Parênteses não fazem "tuple comprehension": fazem um gerador. Use
tuple(…). - Vírgula no lugar do dois-pontos dá
SyntaxError; faltar ochave:não dá erro, mas cria um conjunto. - Se a regra não cabe numa linha clara, prefira o
forcomum.