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 dedict[chave]quando a ausência da chave é um caso normal. - Prefira
defaultdictasetdefaultpara agrupamentos — o código fica mais limpo. - Use
Counterpara contagens — já vem com operações aritméticas emost_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