Voltar ao Glossario
Glossario Python

Dicionário Python: O que É e Como Funciona | Python Brasil

Guia completo sobre dicionários em Python: defaultdict, Counter, ChainMap, hash tables, pattern matching, performance O(1) e exemplos práticos.

O que é um Dicionário em Python?

Um dicionário (dict) é uma estrutura de dados que armazena pares de chave-valor. É uma das estruturas mais utilizadas em Python e funciona como uma tabela de consulta rápida — você busca um valor pela sua chave em tempo constante.

Dicionários são mutáveis, ordenados (mantêm a ordem de inserção a partir do Python 3.7) e não permitem chaves duplicadas. São tão fundamentais que o próprio interpretador Python os usa internamente para armazenar variáveis locais, atributos de objetos e namespaces de módulos.

Como criar e usar

# Criando dicionários
aluno = {
    'nome': 'Ana Silva',
    'idade': 22,
    'curso': 'Ciência da Computação',
    'notas': [8.5, 9.0, 7.8]
}

# dict() construtor
config = dict(host='localhost', porta=5432, banco='producao')

# Acessando valores
print(aluno['nome'])           # Ana Silva
print(aluno.get('email'))      # None — sem KeyError
print(aluno.get('email', '')) # '' — valor padrão personalizado

# Adicionando e atualizando
aluno['email'] = 'ana@email.com'
aluno['idade'] = 23

# Removendo
del aluno['notas']
email = aluno.pop('email')           # remove e retorna o valor
ultimo = aluno.popitem()             # remove e retorna o último par (Python 3.7+)

# Iterando
for chave, valor in aluno.items():
    print(f"{chave}: {valor}")

Implementação interna: tabela de hash

Dicionários Python são implementados como tabelas de hash, o que garante desempenho O(1) amortizado para as operações fundamentais:

import timeit

# Busca em lista — O(n), escala linearmente
lista_grande = list(range(1_000_000))
tempo_lista = timeit.timeit(lambda: 999_999 in lista_grande, number=1000)

# Busca em dict — O(1), tempo constante independente do tamanho
dict_grande = {i: True for i in range(1_000_000)}
tempo_dict = timeit.timeit(lambda: 999_999 in dict_grande, number=1000)

print(f"Lista: {tempo_lista:.4f}s")   # ~0.025s
print(f"Dict:  {tempo_dict:.4f}s")    # ~0.00004s (centenas de vezes mais rápido)

# Operações e suas complexidades:
# dict[key]           — O(1) amortizado
# key in dict         — O(1) amortizado
# dict[key] = value   — O(1) amortizado
# del dict[key]       — O(1) amortizado
# len(dict)           — O(1)
# Iteração completa   — O(n)

Chaves customizadas: hash e eq

Para usar objetos personalizados como chaves de dicionário, eles precisam implementar __hash__ e __eq__:

class Ponto:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __hash__(self):
        # Tuplas são hashable — delegamos o hash para elas
        return hash((self.x, self.y))

    def __eq__(self, other):
        if not isinstance(other, Ponto):
            return NotImplemented
        return self.x == other.x and self.y == other.y

    def __repr__(self):
        return f"Ponto({self.x}, {self.y})"

# Agora Ponto pode ser chave de dicionário
mapa = {}
p1 = Ponto(1, 2)
mapa[p1] = "origem relativa"
print(mapa[Ponto(1, 2)])   # "origem relativa" — igualdade por valor!

setdefault e dict.fromkeys

# setdefault — retorna o valor existente ou insere e retorna o padrão
contagem = {}
for letra in 'abracadabra':
    contagem.setdefault(letra, 0)
    contagem[letra] += 1
print(contagem)   # {'a': 5, 'b': 2, 'r': 2, 'c': 1, 'd': 1}

# Agrupamento com setdefault
alunos = [('SP', 'Ana'), ('RJ', 'Bruno'), ('SP', 'Carla')]
por_estado = {}
for estado, nome in alunos:
    por_estado.setdefault(estado, []).append(nome)
# {'SP': ['Ana', 'Carla'], 'RJ': ['Bruno']}

# dict.fromkeys — cria dicionário com chaves de um iterável
chaves = ['host', 'porta', 'banco']
config_vazia = dict.fromkeys(chaves, None)
# {'host': None, 'porta': None, 'banco': None}

# CUIDADO: valor mutável é compartilhado entre todas as chaves
# Ruim — todas as chaves apontam para a MESMA lista
errado = dict.fromkeys(['a', 'b', 'c'], [])
errado['a'].append(1)
print(errado)   # {'a': [1], 'b': [1], 'c': [1]} — surpresa!

# Certo — use defaultdict ou dict comprehension
correto = {k: [] for k in ['a', 'b', 'c']}

defaultdict — valores padrão automáticos

from collections import defaultdict

# defaultdict(factory) chama factory() sempre que uma chave não existe
contagem = defaultdict(int)    # int() retorna 0
for letra in 'abracadabra':
    contagem[letra] += 1       # sem KeyError, sem setdefault

print(dict(contagem))   # {'a': 5, 'b': 2, 'r': 2, 'c': 1, 'd': 1}

# Agrupamento simples
por_estado = defaultdict(list)
alunos = [('SP', 'Ana'), ('RJ', 'Bruno'), ('SP', 'Carla')]
for estado, nome in alunos:
    por_estado[estado].append(nome)

# Defaultdict aninhado (grafo de adjacência)
grafo = defaultdict(lambda: defaultdict(int))
grafo['A']['B'] += 1
grafo['A']['C'] += 2
grafo['B']['A'] += 1
print(dict(grafo['A']))   # {'B': 1, 'C': 2}

Counter — contagem automática de elementos

from collections import Counter

# Contar elementos de um iterável
texto = "o rato roeu a roupa do rei de roma"
palavras = Counter(texto.split())
print(palavras.most_common(3))
# [('o', 2), ('de', 1), ('rato', 1)] — ordem pode variar nos empates

# Operações aritméticas entre Counters
c1 = Counter(a=3, b=1)
c2 = Counter(a=1, b=2)
print(c1 + c2)   # Counter({'a': 4, 'b': 3})
print(c1 - c2)   # Counter({'a': 2})  — descarta negativos
print(c1 & c2)   # Counter({'a': 1, 'b': 1}) — mínimo de cada
print(c1 | c2)   # Counter({'a': 3, 'b': 2}) — máximo de cada

# Frequência de caracteres
letras = Counter('mississippi')
print(letras['s'])                      # 4
print(letras.most_common(2))            # [('i', 4), ('s', 4)]
print(sum(letras.values()))             # total de elementos = 11

OrderedDict — dicionário com operações de ordem

Desde o Python 3.7, dict mantém ordem de inserção. OrderedDict ainda é útil por suas operações exclusivas:

from collections import OrderedDict

od = OrderedDict()
od['primeiro'] = 1
od['segundo'] = 2
od['terceiro'] = 3

# move_to_end — move uma chave para o início ou fim
od.move_to_end('primeiro')       # move para o fim (padrão)
od.move_to_end('terceiro', last=False)  # move para o início
print(list(od.keys()))   # ['terceiro', 'segundo', 'primeiro']

# Implementando LRU cache manual
class LRUCache:
    def __init__(self, capacidade):
        self.cache = OrderedDict()
        self.capacidade = capacidade

    def get(self, chave):
        if chave not in self.cache:
            return -1
        self.cache.move_to_end(chave)
        return self.cache[chave]

    def put(self, chave, valor):
        if chave in self.cache:
            self.cache.move_to_end(chave)
        self.cache[chave] = valor
        if len(self.cache) > self.capacidade:
            self.cache.popitem(last=False)   # remove o mais antigo

ChainMap — encadeando dicionários

from collections import ChainMap

# ChainMap busca em múltiplos dicionários sem copiar
padrao = {'tema': 'claro', 'idioma': 'pt-br', 'debug': False}
usuario = {'tema': 'escuro'}
cli_args = {}

config = ChainMap(cli_args, usuario, padrao)
print(config['tema'])    # 'escuro' — usuario sobrescreve padrao
print(config['idioma'])  # 'pt-br' — vem do padrao
print(config['debug'])   # False — vem do padrao

# Modificações afetam apenas o primeiro mapa
config['novo'] = 'valor'
print(cli_args)   # {'novo': 'valor'}
print(padrao)     # inalterado

# Útil para configurações em camadas (CLI > env vars > arquivo > padrões)
import os
config_final = ChainMap(
    dict(arg.split('=') for arg in [] if '=' in arg),  # args CLI
    os.environ,                                          # variáveis de ambiente
    padrao                                               # valores padrão
)

Walrus operator com dicionários

# Python 3.8+ — atribuição dentro de expressões
dados = [{'nome': 'Ana', 'score': 85}, {'nome': 'Bruno', 'score': 45}]

# Processar apenas registros que passam em uma condição
aprovados = [
    resultado
    for d in dados
    if (resultado := {'nome': d['nome'], 'status': 'aprovado', 'score': d['score']})
    and d['score'] >= 60
]

# Evitar busca dupla com walrus
cache = {'pi': 3.14159}
chave = 'pi'
if (valor := cache.get(chave)) is not None:
    print(f"Cache hit: {valor}")
else:
    print("Cache miss")

Structural Pattern Matching com dicionários (Python 3.10+)

def processar_comando(comando):
    match comando:
        case {'acao': 'criar', 'nome': nome, 'tipo': tipo}:
            print(f"Criando {tipo}: {nome}")
        case {'acao': 'deletar', 'id': id_item}:
            print(f"Deletando item {id_item}")
        case {'acao': 'buscar', 'query': query, **extras}:
            print(f"Buscando '{query}' com filtros: {extras}")
        case {'erro': mensagem}:
            print(f"Erro recebido: {mensagem}")
        case _:
            print("Comando não reconhecido")

processar_comando({'acao': 'criar', 'nome': 'relatório', 'tipo': 'PDF'})
# Criando PDF: relatório

processar_comando({'acao': 'buscar', 'query': 'Python', 'pagina': 1})
# Buscando 'Python' com filtros: {'pagina': 1}

Métodos e operadores úteis

d1 = {'a': 1, 'b': 2}
d2 = {'b': 10, 'c': 3}

# Merge (Python 3.9+) — d2 sobrescreve d1 em conflitos
merged = d1 | d2          # {'a': 1, 'b': 10, 'c': 3}
d1 |= d2                  # atualiza d1 no lugar

# update — equivalente ao |= para versões antigas
d1.update(d2)

# Dict comprehension para transformar
precos = {'café': 8.0, 'leite': 5.5, 'pão': 3.0}
com_desconto = {prod: preco * 0.9 for prod, preco in precos.items()}

# Filtrar por condição
caros = {k: v for k, v in precos.items() if v > 5}
# {'café': 8.0, 'leite': 5.5}

Erros comuns

# Erro 1: acessar chave inexistente sem get()
d = {'nome': 'Ana'}
# d['email']   # KeyError!
d.get('email', 'não informado')   # seguro

# Erro 2: modificar o tamanho do dict durante iteração
d = {'a': 1, 'b': 2, 'c': 3}
# for k in d:
#     if k == 'b':
#         del d[k]   # RuntimeError!
# Solução: itere sobre uma cópia das chaves
for k in list(d.keys()):
    if k == 'b':
        del d[k]

# Erro 3: usar tipo mutável como chave
# d[[1,2]] = 'valor'   # TypeError: unhashable type: 'list'
d[(1, 2)] = 'valor'    # tupla é hashable

Boas práticas

  • Use .get(chave, padrao) em vez de dict[chave] quando a ausência da chave é um caso normal.
  • Prefira defaultdict a setdefault para agrupamentos — o código fica mais limpo.
  • Use Counter para contagens — já vem com operações aritméticas e most_common().
  • Utilize dict comprehension para criar dicionários transformados — mais expressivo que loops.
  • Em Python 3.9+, use o operador | para merge de dicionários.

Termos Relacionados

  • Tupla — Estrutura de dados imutável e hashable, usável como chave de dict
  • List Comprehension — Dict comprehension segue a mesma lógica
  • Classe — Dicionários podem representar objetos simples