Skip to content
This page has been auto-translated and may contain errors.View in English

Dicionários ​

Listas permitem que você procure algo pela posição. Mas muitas vezes você quer procurar algo pelo nome. Não "me dê o item 3", mas "me dê a pontuação da Alice". Um dicionário guarda dados em pares de chave-valor: você busca um valor pela sua chave, não pela sua posição.

Quando o índice posicional de uma lista não tem significado, um dicionário é a estrutura certa. Dicts mapeiam chaves arbitrárias para valores, dando a você busca por nome em tempo O(1). Um placar de líderes, uma resposta JSON, um arquivo de configuração: todos são naturalmente expressos como mapeamentos de chave-valor.

Um dict é uma estrutura de chave-valor com busca, inserção e remoção em O(1) na média (O(1) significa que o custo permanece constante independentemente do tamanho do dict, porque as chaves são encontradas por hashing, transformando a chave em um número que aponta direto para o seu slot). As chaves precisam ser hashable, ou seja, seu conteúdo nunca muda, para que esse número permaneça estável: str, int e tuple se qualificam, list não. Os valores podem ser qualquer objeto. Desde o Python 3.7, os dicts mantêm as chaves na ordem em que foram inseridas, o que importa sempre que você for serializar ou exibir um dict. Essa mesma estrutura está por trás de boa parte do Python, incluindo os atributos dos seus próprios objetos, então vale a pena conhecer dicts a fundo. O capítulo de Listas cobre a estrutura equivalente baseada em posição.

Criando um dicionário ​

Chaves (as chaves de curva, {}) com dois-pontos entre cada chave e valor, e vírgulas entre os pares. As chaves quase sempre são strings. Os valores podem ser qualquer coisa: números, strings, outras listas, até outros dicionários.

Literais de dict usam chaves de curva com a sintaxe chave: valor. As chaves podem ser de qualquer tipo imutável (hashable): strings, inteiros, tuplas. Os valores podem ser qualquer objeto Python. Dicts preservam a ordem de inserção, então, quando você itera, os itens aparecem na ordem em que foram adicionados.

Literais de dict são avaliados da esquerda para a direita. As chaves precisam ser hashable (seu conteúdo não pode mudar, para que o número sob o qual o Python as arquiva permaneça fixo): str, int e tuple se qualificam, list e dict não. Os valores não têm essa restrição. A ordem de inserção faz parte da garantia da linguagem desde o Python 3.7, então iteração e serialização são reproduzíveis. Uma pegadinha silenciosa: uma chave duplicada num mesmo literal não gera erro, apenas mantém o último valor e descarta o anterior.

python
player = {
    "name": "Alice",
    "score": 87,
    "level": 5,
    "alive": True,
}
JunoCriando um dicionário Chaves de curva, dois-pontos entre cada chave e seu valor, vírgulas entre os pares. As chaves geralmente são strings, os valores podem ser qualquer coisa, até outro dicionário. A ordem em que você adiciona as coisas é a ordem em que você as recebe de volta, coisa que eu uso mais do que esperava.
JunoCriando um dicionário Chaves de curva com pares chave: valor. As chaves podem ser de qualquer tipo imutável (str, int, tuple), os valores podem ser qualquer objeto. A iteração segue a ordem de inserção, então um dict funciona bem quando a ordem importa.
JunoCriando um dicionário As chaves precisam ser hashable, então str, int e tuple valem, list e dict não. A ordem de inserção é garantida desde o 3.7, então você pode contar com ela na saída. Cuidado com o literal de chave duplicada: ele mantém o último valor silenciosamente, sem aviso.

Acessando valores ​

Use colchetes com a chave para obter o valor. Se a chave não existir, o Python lança um KeyError. Use .get() quando você não tiver certeza se a chave está lá: ele retorna None em vez de travar, ou um valor padrão que você especificar.

O acesso por colchetes lança KeyError para uma chave ausente. .get(key) retorna None quando não encontra. .get(key, default) retorna o padrão em vez disso. Use .get() sempre que a presença da chave for incerta; é mais seguro e mais legível do que envolver o acesso num try/except.

O acesso por colchetes busca a chave fazendo hash nela e lendo o slot correspondente, o que é O(1) na média (custo constante independente do tamanho). Se não encontrar, lança KeyError. .get(key, default=None) faz a mesma busca, mas retorna o padrão em vez de lançar erro quando não encontra, então é a opção certa quando a presença de uma chave é incerta. Para verificar antes de um acesso por colchete, key in d também é O(1); use in quando você só precisa de um sim ou não, e .get() quando quiser o valor ou um substituto em um só passo.

python
player = {"name": "Alice", "score": 87}

player["name"]    # "Alice"
player["score"]   # 87
player["lives"]   # KeyError (a chave não existe)
python
player.get("score")          # 87
player.get("lives")          # None (sem erro, retorna None por padrão)
player.get("lives", 3)       # 3   (usa esse padrão se a chave não existir)

.get() é mais seguro sempre que uma chave pode estar ausente:

python
count = inventory.get("arrows", 0)   # 0 se "arrows" não estiver no dict
JunoAcessando valoresd["key"] te entrega o valor, mas lança KeyError se essa chave não existir. Quando você não tem certeza, use .get(): ele retorna None em vez de travar, ou um padrão que você passar. Hoje eu coloco padrão em tudo, e meus programas pararam de travar por chaves ausentes.
JunoAcessando valores O acesso por colchetes lança KeyError quando não encontra, .get(key) retorna None, e .get(key, default) retorna o que você passar. Use .get() quando uma chave pode estar ausente: mais limpo do que envolver o acesso num try/except.
JunoAcessando valoresd[key] e .get() fazem a mesma busca O(1), a diferença está só no que acontece quando não encontram: lançar erro ou retornar um padrão. Use key in d quando quiser um sim ou não, .get() quando quiser o valor ou um substituto em um único passo.

Adicionando e atualizando ​

Atribua a uma chave usando colchetes. Se a chave já existir, o valor é substituído. Se ainda não existir, uma nova entrada é criada. Use .update() para mesclar um dicionário inteiro de uma vez.

Atribuir a uma chave é O(1) na média e faz as duas coisas: cria a entrada se a chave for nova, substitui o valor se ela já existir. .update() aceita outro dict ou um iterável de pares chave-valor e aplica essa mesma atribuição para cada entrada, sobrescrevendo as chaves existentes.

d[key] = value faz hash na chave e insere ou sobrescreve em um único passo O(1) na média, então não existe um "adicionar" e um "atualizar" separados: a atribuição faz as duas coisas. .update(other) é essa mesma operação executada para cada entrada de other, sobrescrevendo qualquer chave compartilhada. Para mesclar sem alterar o original, o operador | (Python 3.9+) retorna um novo dict e deixa ambas as entradas intactas, enquanto |= altera o dict no lugar, como o .update(). Use | quando uma função não deve modificar um dict que quem a chamou ainda está usando.

python
player = {"name": "Alice", "score": 87}

player["score"] = 92        # atualiza um valor existente
player["level"] = 5         # adiciona uma nova chave
python
extras = {"level": 5, "alive": True}
player.update(extras)   # adiciona/sobrescreve com as chaves de extras
JunoAdicionando e atualizando Atribua a uma chave usando colchetes: se ela já existir, o valor é substituído; se não, uma nova entrada aparece. Uma única sintaxe cuida das duas coisas, sem precisar checar antes. Use .update() para incorporar um dicionário inteiro de uma vez.
JunoAdicionando e atualizandod[key] = value cria ou substitui num único movimento, então não existe uma distinção entre adicionar e atualizar. .update(other) mescla um dict inteiro, sobrescrevendo as chaves compartilhadas. As duas operações são O(1) por chave.
JunoAdicionando e atualizando A atribuição insere ou sobrescreve em um único passo O(1), e o .update() é isso repetido para cada entrada. Quando você não quer alterar o original, | retorna um novo dict mesclado, enquanto |= altera no lugar. Use | dentro de uma função para não modificar sem querer um dict que quem chamou ainda está usando.

Removendo itens ​

Quatro formas de remover entradas. .pop() remove uma chave e devolve o valor. .pop() com um padrão é seguro quando a chave pode não existir. del remove uma chave sem retornar nada. .clear() esvazia o dicionário inteiro.

.pop(key) lança KeyError quando não encontra. .pop(key, default) retorna o padrão em vez disso, o que faz dele o método de remoção seguro. del d[key] remove a chave sem retornar nada e lança KeyError quando não encontra. .clear() remove todas as entradas, mas mantém o próprio objeto dict.

.pop(key, default) remove e retorna em uma única busca O(1) na média, e o padrão torna a operação segura contra ausência; sem ele, uma chave ausente lança KeyError. del d[key] remove sem retornar nada e lança erro quando não encontra. .clear() esvazia o dict, mas mantém o mesmo objeto, então qualquer outro nome que apontar para ele também vê um dict vazio. O ponto de falha para lembrar: alterar um dict enquanto você o itera lança RuntimeError no meio do loop. A solução é fazer uma cópia das chaves que você planeja remover primeiro, com for key in list(d):, e então remover do original.

python
player = {"name": "Alice", "score": 87, "level": 5}

player.pop("level")            # remove "level" e retorna 5
player.pop("lives", None)      # pop seguro, retorna None se a chave não existir
del player["score"]            # remove "score", sem valor de retorno
player.clear()                 # remove tudo

.pop() com um padrão é a forma mais segura de remover uma chave que pode não existir.

JunoRemovendo itens.pop(key) remove uma chave e te devolve o valor, e .pop(key, None) fica tranquilo quando a chave pode não estar lá. del d[key] remove sem retornar nada, .clear() esvazia tudo. O padrão no .pop() me salvou de muitos sustos com KeyError.
JunoRemovendo itens.pop(key) lança erro quando não encontra, .pop(key, default) não, o que faz dele a escolha segura. del d[key] remove sem retorno e lança erro quando não encontra; .clear() esvazia o dict, mas mantém o objeto em si.
JunoRemovendo itens.pop(key, default) é a sua remoção segura que também retorna o valor; del e o pop por colchete lançam erro quando a chave falta. O que costuma pegar todo mundo: remover durante a iteração gera RuntimeError. Faça uma cópia com for key in list(d): primeiro, depois delete do original.

Iterando ​

Três visões permitem percorrer diferentes partes de um dicionário. Iterar o dict diretamente te dá as chaves. .values() dá os valores. .items() dá os dois ao mesmo tempo e é o que você vai usar mais: descompacte cada par em dois nomes para loops limpos e legíveis.

.keys(), .values() e .items() retornam objetos de visão (views), não listas. As views refletem o estado atual do dict de forma dinâmica: se você modificar o dict, a view é atualizada imediatamente. .items() é a mais útil na maioria dos loops, porque a descompactação em tupla for k, v in d.items() fica clara de ler.

.keys(), .values() e .items() retornam objetos de visão (janelas em tempo real sobre o dict, não listas separadas). Uma view não copia nada e reflete o estado atual do dict, então, se o dict mudar, a view muda junto. Como as chaves são únicas e hashable, a view de chaves suporta álgebra de conjuntos: d.keys() & other.keys() encontra chaves compartilhadas, - encontra a diferença, o que é útil para comparar duas configurações. A mesma armadilha de alterar durante a iteração se aplica aqui; se você precisar mudar o dict durante o loop, itere sobre uma cópia com list(d.items()).

python
player = {"name": "Alice", "score": 87, "level": 5}

for key in player:               # itera as chaves (o mais comum)
    print(key)

for key in player.keys():        # o mesmo, view explícita de chaves
    print(key)

for value in player.values():    # os valores
    print(value)

for key, value in player.items():   # os dois, o mais útil
    print(f"{key}: {value}")

.items() é o que você vai usar mais. Descompactar cada par em dois nomes torna o loop legível.

JunoIterando Percorra um dict diretamente e você recebe suas chaves. .values() dá os valores, e .items() dá os dois ao mesmo tempo, que é o que você vai usar mais. Descompactar cada par em dois nomes, como em for key, value in player.items(), deixou meus loops muito mais fáceis de ler.
JunoIterando.keys(), .values() e .items() retornam views em tempo real, não listas, então elas acompanham o dict conforme ele muda. .items() com for k, v in d.items() fica com a leitura mais limpa e é o loop que você vai escrever mais.
JunoIterando As três views são janelas em tempo real, não cópias, então refletem o dict conforme ele muda. A view de chaves faz álgebra de conjuntos, então d.keys() & other.keys() compara dois dicts em uma linha. Mesma regra da remoção: não altere durante a iteração, faça uma cópia com list(d.items()) se precisar.

Verificando pertencimento ​

in verifica se uma chave existe no dicionário. Ele não verifica valores, só chaves. Para verificar se algo não está presente, use not in.

in e not in são O(1) para dicts, e verificam apenas chaves. Para verificar valores, você usaria in d.values(), mas isso é O(n), já que valores não são indexados.

key in d encontra a chave fazendo hash nela e lendo um único slot: O(1) na média, constante independente do tamanho do dict. value in d.values() precisa percorrer os valores um a um: O(n), com o custo crescendo junto com o tamanho. Essa assimetria é o motivo pelo qual você deve guardar o que vai buscar como chaves, em vez de varrer valores: se você se pega buscando por valores com frequência, provavelmente quer o dict organizado ao contrário, ou um segundo dict mapeando valor de volta para chave.

python
player = {"name": "Alice", "score": 87}

"name"  in player      # True
"lives" in player      # False
"lives" not in player  # True

in verifica apenas as chaves. Para verificar valores, use in player.values(), embora isso raramente seja necessário.

JunoVerificando pertencimentoin te diz se uma chave está no dict, e só uma chave, nunca um valor. Continua rápido não importa o tamanho do dict. Inverta para not in para verificar que algo está ausente.
JunoVerificando pertencimentoin e not in verificam apenas chaves, em O(1). Verificar um valor precisa de value in d.values(), que é O(n) porque valores não são indexados. Por isso, projete pensando em buscar as coisas pela chave.
JunoVerificando pertencimentokey in d é O(1), value in d.values() é O(n). Essa diferença é o motivo para organizar um dict pela coisa que você usa para buscar. Se você continua varrendo valores, seu dict está organizado do jeito errado; adicione um mapa inverso em vez disso.

Dicionários aninhados ​

Valores podem ser dicionários eles mesmos. É assim que você representa dados estruturados em múltiplos níveis: um jogador que tem uma seção de estatísticas, um arquivo de configuração com subsseções. Dois conjuntos de colchetes acessam um valor aninhado: o primeiro escolhe a chave externa, o segundo escolhe a chave interna.

Dicts aninhados são dicts em que os valores são, eles mesmos, dicts. O acesso é feito com colchetes encadeados. Alterar um dict interno afeta o dict externo, porque o dict externo guarda uma referência ao mesmo objeto. Mantenha o aninhamento raso sempre que possível: aninhamento profundo rapidamente fica difícil de ler e navegar.

Um dict aninhado guarda referências aos dicts internos, não cópias deles (uma referência é um apontador para um único objeto compartilhado, então dois nomes podem apontar para o mesmo dict interno). É aqui que copiar pega mal: d.copy() é uma cópia superficial (shallow copy), ela duplica o dict externo, mas os dicts internos continuam compartilhados, então alterar um deles através da cópia também altera o original. Quando você precisar de uma cópia totalmente independente, copy.deepcopy() percorre a árvore inteira e duplica cada nível. Use isso antes de entregar uma configuração a um código que pode alterá-la. Cada passo com colchete é sua própria busca O(1), então a profundidade não custa nada em velocidade, só em legibilidade.

python
users = {
    "alice": {"score": 87, "level": 5},
    "bob": {"score": 74, "level": 3},
}

users["alice"]["score"]   # 87
users["bob"]["level"]     # 3

O acesso é feito com colchetes encadeados. Para estruturas profundamente aninhadas, isso pode ficar difícil de gerenciar, então mantenha o aninhamento raso sempre que puder.

JunoDicionários aninhados Um valor pode ser um dicionário inteiro por si só, e é assim que você guarda dados estruturados, como um jogador com uma seção de estatísticas. Dois conjuntos de colchetes chegam lá dentro: o primeiro escolhe a chave externa, o segundo escolhe algo de dentro dela. É útil, mas eu tento não aninhar demais, senão fica complicado de ler.
JunoDicionários aninhados Dicts aninhados são dicts cujos valores são dicts; chegue lá com colchetes encadeados. O dict externo guarda uma referência ao dict interno, então alterar o dict interno aparece em todo lugar onde ele é referenciado. Mantenha o aninhamento raso, senão fica difícil de navegar.
JunoDicionários aninhados O dict externo guarda referências aos dicts internos, então .copy() é superficial: a cópia compartilha esses dicts internos, e uma alteração vaza para os dois lados. Use copy.deepcopy() quando precisar de independência real. Profundidade não custa nada em velocidade, mas custa em legibilidade, então mantenha raso.

setdefault ​

.setdefault() lê uma chave se ela existir, ou a define com um valor padrão se não existir, e então retorna o valor. É útil quando você precisa que uma chave exista, mas não quer sobrescrevê-la se ela já estiver lá.

.setdefault(key, default) faz um ler-ou-criar em uma única chamada: se a chave existir, retorna o valor atual sem mudar nada; se não existir, insere o padrão e o retorna. O caso de uso comum é construir estruturas agrupadas sem uma verificação de existência separada.

.setdefault(key, default) é uma única busca O(1) que lê-ou-cria: chaves presentes retornam seu valor existente intacto, chaves ausentes recebem default inserido e retornado. O detalhe que vale conhecer: default é um argumento comum, então ele sempre é construído, mesmo quando a chave já existe. Se esse padrão é caro de construir (um objeto novo, uma chamada de função), você paga esse custo em toda chamada. O defaultdict, visto a seguir, evita isso executando uma fábrica só quando não encontra a chave. Para o padrão comum de "agrupar itens em listas", .setdefault(key, []).append(...) é a linha padrão que substitui uma verificação key in d.

python
inventory = {}

inventory.setdefault("arrows", 0)    # define "arrows": 0, retorna 0
inventory.setdefault("arrows", 10)   # "arrows" já existe, sem mudança, retorna 0

É útil para construir estruturas agrupadas sem verificar antes se a chave existe:

python
groups = {}

for name, team in players:
    groups.setdefault(team, []).append(name)
Junosetdefault.setdefault(key, default) lê uma chave se ela estiver lá, ou a define com o padrão e retorna se não estiver. É a forma organizada de garantir que uma chave existe sem atropelar um valor que já está lá. Uso mais para agrupar coisas, sem precisar checar "essa chave já existe?".
Junosetdefault.setdefault(key, default) retorna um valor existente sem alterá-lo, ou insere o padrão e o retorna quando não encontra. O ganho está no agrupamento: d.setdefault(k, []).append(x) monta listas por chave sem uma verificação de existência separada.
Junosetdefault Um único ler-ou-criar O(1), mas o padrão sempre é construído, mesmo quando encontra a chave, então um padrão caro custa em toda chamada. É exatamente isso que a fábrica sob demanda do defaultdict resolve. Para agrupar, .setdefault(k, []).append(x) é a linha que aposenta a checagem key in d.

collections.defaultdict e Counter ​

A biblioteca padrão tem duas subclasses de dict que resolvem padrões comuns automaticamente. defaultdict cria um valor padrão para chaves ausentes, então você nunca recebe um KeyError. Counter conta quantas vezes cada item aparece em uma sequência e devolve o resultado como um dict.

defaultdict recebe um chamável que produz o valor padrão para novas chaves, eliminando a necessidade de .setdefault(). Counter é um dict especializado para contagem de frequência, com um método .most_common(). As duas são subclasses de dict, então todas as operações padrão de dict funcionam com elas.

defaultdict(factory) executa a fábrica (um chamável sem argumentos, como list ou int) na primeira vez que você lê uma chave ausente, guarda o resultado e o retorna, então d[k] nunca lança KeyError. Essa é a diferença chave em relação ao .setdefault(): a fábrica só é executada quando não encontra a chave, então um padrão caro não custa nada no caminho comum. Uma pegadinha para destacar: como uma simples leitura de uma chave ausente a cria, apenas verificar d[k] pode aumentar o dict, então use k in d quando você só quiser testar, não inserir. Counter é um dict feito para contabilizar: passe uma sequência para ele e ele conta as ocorrências, e .most_common(n) retorna os n primeiros por contagem. As duas classes ficam no módulo collections.

defaultdict e Counter ficam na biblioteca padrão, então precisam ser importados antes de usar. Imports recebem o tratamento completo no capítulo de Módulos.

python
from collections import defaultdict

groups = defaultdict(list)
for name, team in players:
    groups[team].append(name)   # sem KeyError se team for novo
python
from collections import Counter

words = ["cat", "dog", "cat", "bird", "cat", "dog"]
counts = Counter(words)
# Counter({'cat': 3, 'dog': 2, 'bird': 1})

counts.most_common(2)   # [('cat', 3), ('dog', 2)]

Counter economiza muito código repetitivo de "contar coisas em um loop".

Junocollections.defaultdict e Counterdefaultdict preenche um padrão para qualquer chave ausente, então agrupar e contar param de te jogar um KeyError na cara. Counter contabiliza quantas vezes cada item aparece em uma sequência e devolve tudo como um dict. Da primeira vez que troquei um loop de contagem escrito à mão por Counter, metade do meu código desapareceu.
Junocollections.defaultdict e Counterdefaultdict(list) ou defaultdict(int) cria automaticamente o padrão numa chave nova, aposentando o .setdefault() para agrupar e contar. Counter é um dict ajustado para frequências, com .most_common(n) já embutido. As duas são subclasses de dict, então tudo que você já sabe continua funcionando.
Junocollections.defaultdict e Counter A fábrica do defaultdict só executa quando não encontra a chave, então, ao contrário do .setdefault(), um padrão caro não custa nada no caminho comum. A pegadinha: ler uma chave ausente a cria, então teste com k in d, não com d[k]. Counter contabiliza uma sequência e .most_common(n) traz os n primeiros.

Na prática ​

Construindo um rastreador de pontuações e imprimindo um resumo com todas as entradas:

python
scores = {"Alice": 87, "Bob": 74, "Carol": 92, "Dave": 55}

total = sum(scores.values())
average = total / len(scores)

print(f"Jogadores: {len(scores)}")
print(f"Média:     {average:.1f}")
print(f"Maior:     {max(scores.values())}")
print(f"Menor:     {min(scores.values())}")
print()

for name, score in scores.items():
    print(f"  {name}: {score}")

Construindo um dict de resultados por arquivo dentro de um loop, e depois resumindo todas as entradas:

python
job_results = {}
files = ["relatorio_jan.csv", "relatorio_fev.csv", "relatorio_mar.csv"]

for filename in files:
    size = len(filename) * 100   # substituto para o tamanho real do arquivo
    if size < 2000:
        status = "ok"
    else:
        status = "large"
    job_results[filename] = {"size": size, "status": status}

ok_count = 0
large_count = 0

for result in job_results.values():
    if result["status"] == "ok":
        ok_count += 1
    else:
        large_count += 1

print(f"Processados {len(job_results)} arquivo(s): {ok_count} ok, {large_count} grandes")

Validando um dict aninhado de requisição ao iterar campos obrigatórios, e depois normalizando um dict de importância de features no próprio lugar:

python
request = {
    "method": "POST",
    "path": "/users",
    "headers": {"Content-Type": "application/json"},
    "body": {"username": "alice", "email": "[email protected]"},
}

body = request["body"]
errors = []

for field in ["username", "email"]:
    if not body.get(field):
        errors.append(f"Campo obrigatório ausente: {field}")

if "email" in body and "@" not in body["email"]:
    errors.append("Formato de e-mail inválido")

print(f"Method: {request['method']} {request['path']}")
if errors:
    print(f"Errors: {errors}")
else:
    print("Validation passed")

# Normaliza os valores de importância de features para somar 1
feature_importance = {"age": 0.34, "income": 0.28, "region": 0.15, "purchases": 0.23}
total = sum(feature_importance.values())

for key in feature_importance:
    feature_importance[key] = round(feature_importance[key] / total, 3)

print(f"Normalised: {feature_importance}")