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

Dicionários

Listas permitem que você busque coisas pela posição. Mas frequentemente você quer buscar algo pelo nome. Não "me dê o item 3", mas "me dê a pontuação da Ana". Um dicionário armazena dados como pares chave-valor: você busca um valor pela sua chave, não pela 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ê buscas nomeadas em tempo O(1). Um placar, uma resposta JSON, um arquivo de configuração: todos são naturalmente expressos como mapeamentos chave-valor.

dict é um armazenamento chave-valor baseado em tabela hash, com busca, inserção e remoção em O(1) em média. As chaves devem ser hasheáveis; valores podem ser qualquer objeto. Desde o Python 3.7, dicts preservam a ordem de inserção. dict é a base para namespaces do Python, atributos __dict__ de objetos e argumentos nomeados.

Criando um dicionário

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

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

Literais de dict são avaliados da esquerda para a direita. As chaves devem ser hasheáveis: str, int, tuple funcionam; list e dict não. Os valores são irrestritos. A ordem de inserção é garantida desde o Python 3.7 (implementada como uma tabela hash compacta desde o 3.6). Chaves duplicadas em um literal silenciosamente usam o último valor.

python
player = {
    "name":  "Ana",
    "score": 87,
    "level": 5,
    "alive": True,
}

Acessando valores

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

O acesso com colchetes levanta KeyError em 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 em um try/except.

d[key] chama __getitem__, que faz hash da chave e investiga a tabela: O(1) em média. Em uma falha, levanta KeyError. .get(key, default=None) realiza a mesma investigação, mas retorna o padrão em uma falha em vez de levantar. A verificação key in d (que chama __contains__) é O(1) e é a maneira idiomática de proteger antes do acesso.

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

player["name"]    # "Ana"
player["score"]   # 87
player["lives"]   # KeyError (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   (use este padrão se a chave estiver ausente)

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

python
count = inventory.get("arrows", 0)   # 0 se "arrows" não estiver no dict

Adicionando e atualizando

Atribua a uma chave com 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 só vez.

A atribuição a uma chave chama __setitem__: O(1) em média, cria ou substitui. .update() aceita outro dict ou um iterável de pares chave-valor e chama __setitem__ para cada entrada, sobrescrevendo chaves existentes.

d[key] = value chama __setitem__, que faz hash da chave e insere ou sobrescreve na tabela: O(1) em média. .update(other) é equivalente a chamadas repetidas de __setitem__. O operador | (Python 3.9+) mescla dicts sem mutação e retorna um novo dict; |= muda no local.

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

player["score"] = 92        # atualiza valor existente
player["level"] = 5         # adiciona nova chave
python
extras = {"level": 5, "alive": True}
player.update(extras)   # adiciona/sobrescreve com as chaves de extras

Removendo itens

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

.pop(key) levanta KeyError em uma falha. .pop(key, default) retorna o padrão em vez disso, tornando-o o método de remoção seguro. del d[key] chama __delitem__ e levanta KeyError em uma falha. .clear() remove todas as entradas, mas mantém o próprio objeto dict.

.pop(key, default) é uma única investigação de hash: O(1) em média. del d[key] chama __delitem__, mesma investigação, levanta em uma falha. Após a remoção, a tabela hash pode encolher para liberar memória. .clear() redefine o tamanho da tabela. Iterar um dict e mutá-lo no mesmo laço levanta RuntimeError; construa primeiro uma lista de chaves a remover.

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

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

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

Iterando

Três visualizações permitem que você percorra diferentes partes de um dicionário. Iterar apenas o dict te dá as chaves. .values() dá os valores. .items() dá ambos de uma vez e é o que você usará mais: desempacote cada par em dois nomes para laços limpos e legíveis.

.keys(), .values() e .items() retornam objetos de visualização, não listas. As visualizações refletem o estado atual do dict dinamicamente: se você modificar o dict, a visualização atualiza imediatamente. .items() é o mais útil para a maioria dos laços, porque o desempacotamento de tupla for k, v in d.items() é claro de ler.

.keys(), .values() e .items() retornam os objetos de visualização dict_keys, dict_values e dict_items. As visualizações são lazy: não copiam dados e atualizam quando o dict subjacente muda. dict_keys suporta álgebra de conjuntos (&, |, -), pois as chaves são únicas e hasheáveis. Mutar um dict durante a iteração levanta RuntimeError; use list(d.items()) para fazer um snapshot, se necessário.

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

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

for key in player.keys():        # mesma coisa, visualização explícita de chaves
    print(key)

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

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

.items() é o que você usará mais. Desempacotar cada par em dois nomes torna o laço legível.

Verificando pertencimento

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

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

key in d chama dict.__contains__, que faz hash da chave e investiga a tabela: O(1) em média. value in d.values() itera a visualização de valores: O(n). Essa assimetria é uma razão central para preferir chaves de dict para busca em vez de escanear valores.

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

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

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

Dicionários aninhados

Valores podem ser dicionários eles mesmos. É assim que você representa dados estruturados com múltiplos níveis: um jogador que tem uma seção de estatísticas, um arquivo de configuração com subseçõ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 próprios dicts. Acesse com subscritos encadeados. Mutar um dict interno afeta o dict externo, porque o dict externo mantém uma referência ao mesmo objeto. Mantenha o aninhamento raso quando possível: aninhamento profundo rapidamente se torna difícil de ler e navegar.

Dicts aninhados armazenam referências de objetos, não cópias. A cópia rasa do dict externo (d.copy()) não copia os dicts internos; mutações nos dicts internos são visíveis tanto pelo original quanto pela cópia. Para estruturas profundamente aninhadas, copy.deepcopy() cria cópias totalmente independentes. Chamadas encadeadas de __getitem__ são cada uma O(1), então a profundidade de acesso não tem custo assintótico.

python
users = {
    "ana":   {"score": 87, "level": 5},
    "bruno": {"score": 74, "level": 3},
}

users["ana"]["score"]      # 87
users["bruno"]["level"]    # 3

Acesse com colchetes encadeados. Para estruturas profundamente aninhadas, isso pode ficar complicado, então mantenha o aninhamento raso quando puder.

setdefault

.setdefault() lê uma chave se ela existir, ou define-a como 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 já estiver lá.

.setdefault(key, default) é uma operação atômica de ler-ou-criar: se a chave existir, retorna seu valor atual sem alterar nada; se não, insere o padrão e o retorna. O caso de uso comum é construir estruturas agrupadas sem uma verificação separada de existência.

.setdefault(key, default) é uma única investigação de hash: O(1) em média. Se a chave estiver ausente, default é inserido e retornado. Se presente, o valor existente é retornado e default é ignorado (nunca avaliado após a verificação). Para o padrão comum de "agrupar itens em listas", esta é a alternativa padrão para verificar key in d antes de fazer append.

python
inventory = {}

inventory.setdefault("arrows", 0)    # define "arrows": 0, retorna 0
inventory.setdefault("arrows", 10)   # "arrows" já existe, sem alteração, retorna 0

É útil para construir estruturas agrupadas sem verificar primeiro a existência da chave:

python
groups = {}

for name, team in players:
    groups.setdefault(team, []).append(name)

collections.defaultdict e Counter

A biblioteca padrão tem duas subclasses de dict que lidam com 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 te dá os resultados como um dict.

defaultdict recebe um callable 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(). Ambos são subclasses de dict, então todas as operações padrão de dict funcionam neles.

defaultdict.__missing__ chama a factory e armazena o resultado, tornando-o seguro em threads para o caso comum. Counter é subclasse de dict e adiciona .most_common(n) (O(n log n) via heapq), .subtract() e operadores aritméticos para combinar contagens. Ambos estão em collections; imports são abordados no capítulo Módulos.

import de collections

defaultdict e Counter precisam ser importados da biblioteca padrão. Imports são abordados no capítulo Módulos.

python
from collections import defaultdict

groups = defaultdict(list)
for name, team in players:
    groups[team].append(name)   # sem KeyError se o time 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 poupa muito código repetitivo de "contar coisas em um laço".

Na prática

Construindo um rastreador de pontuação e imprimindo um resumo com todas as entradas:

python
scores = {"Ana": 87, "Bruno": 74, "Carla": 92, "Diego": 55}

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

print(f"Players:  {len(scores)}")
print(f"Average:  {average:.1f}")
print(f"Highest:  {max(scores.values())}")
print(f"Lowest:   {min(scores.values())}")
print()

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

Construindo um dict de resultados por arquivo em um laço, depois resumindo todas as entradas:

python
job_results = {}
files       = ["report_jan.csv", "report_feb.csv", "report_mar.csv"]

for filename in files:
    size = len(filename) * 100   # placeholder 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"Processed {len(job_results)} file(s): {ok_count} ok, {large_count} large")

Validando um dict de requisição aninhado iterando os campos obrigatórios, depois normalizando um dict de importância de features no local:

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

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

for field in ["username", "email"]:
    if not body.get(field):
        errors.append(f"Missing required field: {field}")

if "email" in body and "@" not in body["email"]:
    errors.append("Invalid email format")

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

# Normaliza os valores de importância das features para somarem 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}")