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

Dicionários

docs.scrimba.com

Listas permitem procurar coisas por posição. Mas frequentemente você quer procurar algo pelo nome. Não "me dê o item 3", mas "me dê a pontuação de Maria". Um dicionário armazena dados como pares chave-valor: você procura um valor por sua chave, não por sua posição.

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

Um dict é um armazenamento chave-valor com busca, inserção e exclusão médias O(1) (O(1) significa que o custo permanece plano não importa o tamanho do dict, porque as chaves são encontradas por hashing, transformando a chave em um número que aponta diretamente para seu slot). As chaves devem ser hashable, significando que seus conteúdos nunca mudam 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 Python 3.7, dicts mantêm as chaves na ordem em que você as inseriu, o que importa sempre que você serializa ou exibe um. A mesma estrutura alimenta muita coisa do Python sob a superfície, incluindo os atributos em seus próprios objetos, portanto dicts vale a pena conhecer bem. O capítulo Listas cobre o primo baseado em posição.

Criando um dicionário

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

Os literais dict usam chaves com a sintaxe key: value. As chaves podem ser qualquer tipo imutável (hashable): strings, inteiros, tuplas. Os valores podem ser qualquer objeto Python. Os dicts preservam a ordem de inserção, então quando você itera, obtém itens na ordem em que foram adicionados.

Os literais dict são avaliados da esquerda para a direita. As chaves devem ser hashable (seus conteúdos não podem mudar, para que o número em que Python as arquiva permaneça): str, int e tuple se qualificam, list e dict não. Os valores não têm tal restrição. A ordem de inserção é parte da garantia da linguagem desde Python 3.7, então iteração e serialização são reproduzíveis. Uma armadilha silenciosa: uma chave duplicada em um único literal não levanta exceção, mantém o último valor e descarta o anterior.

python
player = {
    "name": "Maria",
    "score": 87,
    "level": 5,
    "alive": True,
}
JunoCriando um dicionário Chaves, um dois-pontos entre cada chave e seu valor, vírgulas entre pares. As chaves são geralmente strings, os valores podem ser qualquer coisa, até outro dicionário. Seja qual for a ordem em que você adiciona as coisas é a ordem em que você as obtém de volta, o que eu dependo mais do que esperava.
JunoCriando um dicionário Chaves com pares key: value. As chaves podem ser 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 têm que ser hashable, então str, int e tuple estão dentro, list e dict estão fora. A ordem de inserção é garantida desde 3.7, então você pode contar com isso para saída. Observe a chave duplicada no literal: ela pega o último valor silenciosamente, sem aviso.

Acessando valores

Use colchetes com a chave para obter o valor. Se a chave não existe, Python levanta um KeyError. Use .get() quando você não tem certeza de que a chave está lá: ela retorna None em vez de travar, ou um valor padrão que você especifica.

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

O acesso com colchetes procura a chave por hashing e lendo o slot correspondente, que é O(1) em média (custo plano independente do tamanho). Em uma falha levanta KeyError. .get(key, default=None) faz a mesma busca mas retorna o padrão em uma falha em vez de levantar, então é a chamada certa quando a presença de uma chave é incerta. Para proteger antes de um acesso com colchetes, key in d também é O(1); escolha in quando você só precisa de um sim-ou-não, .get() quando você quer o valor ou um fallback em um passo.

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

player["name"]    # "Maria"
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
JunoAcessando valoresd["key"] lhe dá o valor, mas levanta KeyError se essa chave não estiver lá. Quando você não tem certeza, use .get(): ela retorna None em vez de travar, ou um padrão que você passa. Agora eu uso `.get()` para tudo e meus programas pararam de travar em chaves ausentes.
JunoAcessando valores O acesso com colchetes levanta KeyError em uma falha, .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 em um `try/except`.
JunoAcessando valoresd[key] e .get() fazem uma busca O(1), a diferença é apenas o que acontece em uma falha: levantar versus retornar um padrão. Use key in d quando você quer um sim-ou-não, .get() quando você quer o valor-ou-fallback em um único passo.

Adicionando e atualizando

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

Atribuir a uma chave é O(1) em média e faz ambos os trabalhos: cria a entrada se a chave é nova, substitui o valor se já existe. .update() aceita outro dict ou um iterável de pares chave-valor e aplica essa mesma atribuição para cada entrada, sobrescrevendo chaves existentes.

d[key] = value faz hash da chave e insere ou sobrescreve em um passo O(1) em média, portanto não há uma "adição" e "atualização" separadas: a atribuição faz ambas. .update(other) é a mesma operação executada para cada entrada em other, sobrescrevendo em qualquer chave compartilhada. Para mesclar sem mutar o original, o operador | (Python 3.9+) retorna um novo dict e deixa ambas as entradas intocadas, enquanto |= muta no local como .update(). Use | quando uma função não deve modificar um dict que o chamador ainda mantém.

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

player["score"] = 92        # atualizar existente
player["level"] = 5         # adicionar nova chave
python
extras = {"level": 5, "alive": True}
player.update(extras)   # adiciona/sobrescreve com chaves de extras
JunoAdicionando e atualizando Atribua a uma chave com colchetes: se ela já estiver lá o valor é substituído, se não uma nova entrada aparece. Uma sintaxe lida com ambas, nenhuma necessidade de verificar primeiro. Use .update() para incorporar um dicionário inteiro de uma vez.
JunoAdicionando e atualizandod[key] = value cria ou substitui em um movimento, então não há adição versus atualização distinta. .update(other) mescla um dict inteiro, sobrescrevendo qualquer chave compartilhada. Ambas são O(1) por chave.
JunoAdicionando e atualizando A atribuição insere ou sobrescreve em um passo O(1), e .update() é isso repetido por entrada. Quando você não quer tocar o original, | retorna um dict mesclado fresco enquanto |= muta no local. Use | dentro de uma função para não mudar silenciosamente um dict que o chamador ainda mantém.

Removendo itens

Quatro maneiras de remover entradas. .pop() remove uma chave e lhe dá o valor de volta. .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] remove a chave sem valor de retorno e levanta KeyError em uma falha. .clear() remove todas as entradas mas mantém o objeto dict em si.

.pop(key, default) remove e retorna em uma busca O(1) em média, e o padrão o torna miss-safe; sem ele, uma chave ausente levanta KeyError. del d[key] remove sem valor de retorno e levanta em uma falha. .clear() esvazia o dict mas mantém o mesmo objeto, então qualquer outro nome apontando para ele vê um dict vazio também. O modo de falha a lembrar: mutar um dict enquanto você itera levanta RuntimeError no meio do loop. A correção é tirar um snapshot das chaves que você planeja remover primeiro, com for key in list(d):, então remover do original.

python
player = {"name": "Maria", "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 forma mais segura de remover uma chave que pode não existir.

JunoRemovendo itens.pop(key) remove uma chave e lhe dá seu valor de volta, e .pop(key, None) fica calmo quando a chave pode não estar lá. del d[key] remove sem retornar nada, .clear() esvazia tudo. O padrão em .pop() me salvou de muitas surpresas de KeyError.
JunoRemovendo itens.pop(key) levanta em uma falha, .pop(key, default) não, o que a torna a escolha segura. del d[key] remove sem retornar e levanta em uma falha; .clear() esvazia o dict mas mantém o objeto em si.
JunoRemovendo itens.pop(key, default) é seu remove-e-retorne miss-safe; del e bracket-pop levantam em uma chave ausente. O que morde: remover enquanto itera lança `RuntimeError`. Tire um snapshot com for key in list(d): primeiro, depois delete do original.

Iterando

Três visualizações permitem fazer loop em diferentes partes de um dicionário. Iterar o dict diretamente lhe dá as chaves. .values() dá valores. .items() dá ambas de uma vez e é o que você usará mais: desempacote cada par em dois nomes para loops 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 se atualiza imediatamente. .items() é o mais útil para a maioria dos loops porque o desempacotamento de tupla for k, v in d.items() lê com clareza.

.keys(), .values() e .items() retornam objetos de visualização (janelas ao vivo no dict, não listas separadas). Uma visualização não copia nada e reflete o estado atual do dict, então se o dict mudar, a visualização muda com ele. Como as chaves são únicas e hashable, a visualização de chaves suporta álgebra de conjunto: d.keys() & other.keys() encontra chaves compartilhadas, - encontra a diferença, o que é útil para difereçar duas configs. A mesma armadilha mutar-durante-iteração se aplica aqui; se você deve mudar o dict enquanto faz loop, itere sobre um snapshot com list(d.items()).

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

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

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

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

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

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

JunoIterando Faça loop em um dict diretamente e você obtém suas chaves. .values() dá os valores, e .items() dá ambas de uma vez, que é o que você alcançará mais. Desempacotar cada par em dois nomes como for key, value in player.items() tornou meus loops muito mais fáceis de ler.
JunoIterando.keys(), .values() e .items() retornam visualizações ao vivo, não listas, então elas rastreiam o dict enquanto muda. .items() com for k, v in d.items() lê mais limpo e é o loop que você escreverá mais frequentemente.
JunoIterando As três visualizações são janelas ao vivo, não cópias, então elas refletem o dict enquanto muda. A visualização de chaves faz álgebra de conjunto, então d.keys() & other.keys() diferencia dois dicts em uma linha. Mesma regra que remoção: não mute enquanto itera, tire um snapshot com list(d.items()) se você deve.

Verificando associação

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

in e not in são O(1) para dicts, e eles verificam apenas 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 encontra a chave por hashing e lendo um slot: O(1) em média, plano não importa o tamanho do dict. value in d.values() tem que andar pelos valores um por um: O(n), o custo cresce com o tamanho. Essa assimetria é a razão inteira para armazenar o que você procura como chaves em vez de escanear valores: se você se encontra procurando valores frequentemente, você provavelmente quer o dict keyed de outra forma, ou um segundo dict mapeando valor de volta para chave.

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

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

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

JunoVerificando associaçãoin lhe diz se uma chave está no dict, e apenas uma chave, nunca um valor. Ela permanece rápida não importa o tamanho do dict. Inverta para not in para verificar que algo está ausente.
JunoVerificando associaçãoin e not in verificam apenas chaves, em O(1). Verificar um valor precisa value in d.values(), que é O(n) porque os valores não são indexados. Então projete em torno de procurar coisas por chave.
JunoVerificando associaçãokey in d é O(1), value in d.values() é O(n). Essa diferença é a razão para keying um dict no que você procura. Se você continuar escaneando valores, você tem o dict de forma errada; adicione um mapa reverso em vez disso.

Dicionários aninhados

Os valores podem ser dicionários em si. É assim que você representa dados estruturados com múltiplos níveis: um jogador que tem uma seção de stats, um arquivo de config com sub-seções. Dois conjuntos de colchetes acessam um valor aninhado: o primeiro escolhe a chave externa, o segundo escolhe a chave interna.

Dicionários aninhados são dicts cujos 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 onde possível: o aninhamento profundo rapidamente se torna difícil de ler e navegar.

Um dict aninhado mantém referências para os dicts internos, não cópias deles (uma referência é um ponteiro para um objeto compartilhado, então dois nomes podem apontar para o mesmo dict interno). É aqui onde copiar morde: d.copy() é uma cópia rasa, ela duplica o dict externo mas os dicts internos ainda são compartilhados, então mudar um através da cópia o muda no original também. Quando você precisa a cópia completamente independente, copy.deepcopy() anda pela árvore inteira e duplica cada nível. Alcance por ela antes de você passar uma config para código que pode mutá-la. Cada passo de colchete é sua própria busca O(1), então profundidade não custa nada em velocidade, apenas em legibilidade.

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

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

Acesse com colchetes quadrados encadeados. Para estruturas muito aninhadas, isso pode ficar desajeitado, então mantenha o aninhamento raso onde você puder.

JunoDicionários aninhados Um valor pode ser um dicionário inteiro em si, que é como você mantém dados estruturados como um jogador com uma seção de stats. Dois conjuntos de colchetes alcançam: o primeiro escolhe a chave externa, o segundo escolhe de dentro dela. Útil, mas eu tento não aninhar muito profundamente ou fica irritante de ler.
JunoDicionários aninhados Dicts aninhados são dicts cujos valores são dicts; alcance com colchetes encadeados. O dict externo mantém uma referência ao interno, então mudar o dict interno aparece em todos os lugares em que é referenciado. Mantenha o aninhamento raso ou fica difícil de navegar.
JunoDicionários aninhados O dict externo armazena referências para os dicts internos, então .copy() é rasa: a cópia compartilha esses dicts internos e uma mutação vaza entre ambos. Use copy.deepcopy() quando você precisa de independência real. Profundidade é livre em velocidade, cara em legibilidade, então fique raso.

setdefault

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

.setdefault(key, default) é um ler-ou-criar em uma chamada: se a chave existe, retorna seu valor atual sem mudar nada; se não, 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 busca O(1) que lê-ou-cria: chaves presentes retornam seu valor existente intocado, chaves ausentes recebem default inserido e retornado. A pegadilha digna de saber: default é um argumento ordinário, então é sempre construído mesmo quando a chave já existe. Se esse padrão é caro para construir (um objeto fresco, uma chamada de função), você paga por ele a cada chamada. defaultdict, coberto a seguir, contorna isso executando uma factory apenas em uma falha. Para o padrão cotidiano "agrupar itens em listas", .setdefault(key, []).append(...) é o padrão one-liner 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 a existência da chave primeiro:

python
groups = {}

for name, team in players:
    groups.setdefault(team, []).append(name)
Junosetdefault.setdefault(key, default) lê uma chave se ela está lá, ou a define para o padrão e a retorna se não estiver. É a forma arrumada de se certificar de que uma chave existe sem clobbering um valor que já está lá. Eu a uso mais para agrupar coisas, sem verificação "esta chave está aqui ainda?" necessária.
Junosetdefault.setdefault(key, default) retorna um valor existente intocado, ou insere o padrão e o retorna em uma falha. O ganho é agrupamento: d.setdefault(k, []).append(x) constrói listas por chave sem uma verificação de existência separada.
Junosetdefault Uma busca-ou-criação O(1), mas o padrão é sempre construído mesmo em um acerto, então um padrão caro custa você a cada chamada. Isso é exatamente o que a factory on-miss de defaultdict corrige. Para agrupamento, .setdefault(k, []).append(x) é o one-liner que retira a verificação key in d.

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 para que você nunca obtenha um KeyError. Counter conta com que frequência cada item aparece em uma sequência e lhe 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(). Ambas são subclasses de dict, então todas as operações de dict padrão funcionam nelas.

defaultdict(factory) executa a factory (um callable de zero-argumentos, como list ou int) a primeira vez que você lê uma chave ausente, armazena o resultado e o retorna, então d[k] nunca levanta KeyError. Essa é a diferença chave de .setdefault(): a factory apenas é acionada em uma falha, então um padrão caro não custa nada no caminho comum. Uma pegadilha a sinalizar: porque uma leitura simples de uma chave ausente a cria, meramente verificar d[k] pode crescer o dict, então use k in d quando você apenas quer testar, não inserir. Counter é um dict construído para tallying: alimente com uma sequência e ela conta ocorrências, e .most_common(n) retorna o top n por contagem. Ambas vivem no módulo collections.

defaultdict e Counter vivem na biblioteca padrão, então eles precisam de importação primeiro. As importações recebem tratamento completo 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 team é 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 muita tabulação "conte coisas em um loop".

Junocollections.defaultdict e Counterdefaultdict preenche um padrão para qualquer chave ausente, portanto agrupamento e contagem param de lançar KeyError em você. Counter tally com que frequência cada item aparece em uma sequência e o entrega de volta como um dict. A 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) auto-cria o padrão em uma nova chave, aposentando .setdefault() para agrupamento e contagem. Counter é um dict afinado para frequências, com .most_common(n) incorporado. Ambas são subclasses de dict, então tudo o que você sabe ainda funciona.
Junocollections.defaultdict e Counter A factory de defaultdict é executada apenas em uma falha, então diferente de .setdefault() um padrão caro não custa nada no caminho comum. A armadilha: ler uma chave ausente a cria, então teste com k in d, não d[k]. Counter tally uma sequência e .most_common(n) puxa o top n.

Na prática

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

python
scores = {"Maria": 87, "João": 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 em um loop, depois resumindo entre 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 tamanho de arquivo real
    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} large")

Validando um dict de request aninhado iterando campos requeridos, então normalizando um dict de feature importance no local:

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 email inválido")

print(f"Método: {request['method']} {request['path']}")
if errors:
    print(f"Erros: {errors}")
else:
    print("Validação passou")

# Normalizar valores de importância de feature para somar a 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"Normalizado: {feature_importance}")