Dicionários: estrutura chave-valor fundamental

1. Introdução aos Dicionários em Python

Os dicionários são uma das estruturas de dados mais versáteis e poderosas do Python. Eles implementam o conceito de mapeamento chave-valor, onde cada elemento armazenado é associado a uma chave única que permite acesso direto e eficiente ao seu valor correspondente. Diferente de listas ou tuplas, que são indexadas por posições numéricas, os dicionários utilizam chaves personalizadas como identificadores.

A sintaxe básica para criar um dicionário utiliza chaves {} ou a função construtora dict():

# Criando dicionários
vazio1 = {}
vazio2 = dict()

aluno = {"nome": "Carlos", "idade": 25, "curso": "Engenharia"}
print(aluno)  # {'nome': 'Carlos', 'idade': 25, 'curso': 'Engenharia'}

Os dicionários possuem três características fundamentais:
- Mutabilidade: é possível adicionar, remover ou modificar pares após a criação
- Chaves únicas: cada chave aparece apenas uma vez no dicionário
- Chaves imutáveis: as chaves devem ser de tipos imutáveis (strings, números, tuplas), enquanto os valores podem ser de qualquer tipo

2. Criando e Acessando Dicionários

A forma mais comum de criar dicionários é através de literais, especificando pares chave:valor separados por vírgulas:

# Inicialização com literais
contato = {
    "nome": "Ana Silva",
    "email": "ana@email.com",
    "telefone": "(11) 99999-8888",
    "ativo": True
}

# Acesso a valores
print(contato["nome"])       # Ana Silva

# Usando o método get() - mais seguro
print(contato.get("email"))  # ana@email.com
print(contato.get("endereco", "Não informado"))  # Não informado

A diferença crucial entre o acesso direto e o método get() está no tratamento de chaves inexistentes:

# Acesso direto gera KeyError
# print(contato["endereco"])  # KeyError: 'endereco'

# get() retorna None ou valor padrão
resultado = contato.get("endereco")
print(resultado)  # None

3. Manipulação de Dicionários: Adição, Alteração e Remoção

A manipulação de dicionários é intuitiva e direta:

estoque = {"caneta": 50, "caderno": 30}

# Adicionar novo par
estoque["lapis"] = 100
print(estoque)  # {'caneta': 50, 'caderno': 30, 'lapis': 100}

# Atualizar valor existente
estoque["caneta"] = 45
print(estoque)  # {'caneta': 45, 'caderno': 30, 'lapis': 100}

# Remoção com del
del estoque["caderno"]
print(estoque)  # {'caneta': 45, 'lapis': 100}

# Remoção com pop() - retorna o valor removido
item_removido = estoque.pop("lapis")
print(f"Removido: {item_removido}")  # Removido: 100
print(estoque)  # {'caneta': 45}

# popitem() remove e retorna o último par (Python 3.7+)
ultimo_par = estoque.popitem()
print(ultimo_par)  # ('caneta', 45)

# Limpeza total
estoque.clear()
print(estoque)  # {}

# Verificação de existência
config = {"tema": "escuro", "idioma": "pt-BR"}
print("tema" in config)     # True
print("zoom" in config)     # False

4. Métodos Essenciais para Navegação e Iteração

Python oferece métodos específicos para iterar sobre diferentes componentes do dicionário:

vendas = {
    "João": 1500,
    "Maria": 2300,
    "Pedro": 1800,
    "Ana": 2100
}

# Iteração sobre chaves (padrão)
print("Vendedores:")
for vendedor in vendas:
    print(f"- {vendedor}")

# Equivalente explícito com keys()
for vendedor in vendas.keys():
    print(f"- {vendedor}")

# Iteração sobre valores
print("\nValores de venda:")
for valor in vendas.values():
    print(f"R$ {valor:.2f}")

# Iteração sobre pares com desempacotamento
print("\nRelatório completo:")
for nome, total in vendas.items():
    print(f"{nome}: R$ {total:.2f}")

5. Operações Avançadas e Combinando Dicionários

Combinar dicionários é uma operação comum que pode ser feita de várias formas:

# União com update()
dados1 = {"a": 1, "b": 2}
dados2 = {"c": 3, "d": 4}
dados1.update(dados2)
print(dados1)  # {'a': 1, 'b': 2, 'c': 3, 'd': 4}

# Operador | (Python 3.9+)
base = {"nome": "Maria", "idade": 28}
extra = {"cidade": "São Paulo", "profissao": "Engenheira"}
completo = base | extra
print(completo)  # {'nome': 'Maria', 'idade': 28, 'cidade': 'São Paulo', 'profissao': 'Engenheira'}

# Cópia superficial
original = {"chave": [1, 2, 3]}
copia1 = original.copy()
copia2 = dict(original)

# Cópia profunda (necessária para estruturas aninhadas)
from copy import deepcopy
copia_profunda = deepcopy(original)

# Compreensão de dicionários
quadrados = {x: x**2 for x in range(5)}
print(quadrados)  # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

# Filtrando com dict comprehension
pares = {k: v for k, v in quadrados.items() if v % 2 == 0}
print(pares)  # {0: 0, 2: 4, 4: 16}

6. Chaves e Valores: Restrições e Boas Práticas

As chaves em dicionários devem ser imutáveis, enquanto os valores podem ser qualquer objeto Python:

# Chaves válidas
valido = {
    "string": 1,          # strings
    42: "número",         # inteiros
    3.14: "pi",           # floats
    (1, 2): "tupla",      # tuplas (com elementos imutáveis)
    True: "booleano"      # booleanos (são subclasse de int)
}

# Chaves inválidas (geram TypeError)
# invalido = {[1, 2]: "lista"}     # TypeError: unhashable type: 'list'

# Boas práticas com defaultdict
from collections import defaultdict

# Evita verificar existência antes de acessar
contagem = defaultdict(int)
palavras = ["banana", "maçã", "banana", "pera", "banana"]
for palavra in palavras:
    contagem[palavra] += 1
print(dict(contagem))  # {'banana': 3, 'maçã': 1, 'pera': 1}

7. Dicionários no Mundo Real: Casos de Uso Comuns

Contagem de Frequências

texto = "python é uma linguagem python muito poderosa python"
frequencia = {}
for palavra in texto.split():
    frequencia[palavra] = frequencia.get(palavra, 0) + 1
print(frequencia)
# {'python': 3, 'é': 1, 'uma': 1, 'linguagem': 1, 'muito': 1, 'poderosa': 1}

Agrupamento de Dados

alunos = [
    ("Ana", "Matemática"), ("Pedro", "Física"), ("Maria", "Matemática"),
    ("João", "Química"), ("Carla", "Física"), ("Lucas", "Matemática")
]

turmas = {}
for nome, disciplina in alunos:
    if disciplina not in turmas:
        turmas[disciplina] = []
    turmas[disciplina].append(nome)

print(turmas)
# {'Matemática': ['Ana', 'Maria', 'Lucas'], 'Física': ['Pedro', 'Carla'], 'Química': ['João']}

Cache e Memoização

def fibonacci_memo(n, cache={}):
    if n in cache:
        return cache[n]
    if n <= 1:
        return n
    cache[n] = fibonacci_memo(n-1, cache) + fibonacci_memo(n-2, cache)
    return cache[n]

print(fibonacci_memo(10))  # 55
print(fibonacci_memo(50))  # 12586269025 (rápido graças ao cache)

8. Comparação com Estruturas Vizinhas e Considerações Finais

A escolha entre dicionários e outras estruturas depende do caso de uso:

# Quando usar dicionários vs listas
# Dicionário: acesso por chave (O(1))
contato_dict = {"email": "ana@email.com"}
print(contato_dict["email"])  # Rápido: O(1)

# Lista: acesso por índice ou busca linear
contato_list = ["ana@email.com", "(11) 99999-8888"]
# Para encontrar o email, precisamos saber o índice ou buscar

# Performance: busca em dicionário vs lista
import time
grande_dict = {i: i**2 for i in range(100000)}
grande_lista = list(range(100000))

# Busca em dicionário é O(1)
inicio = time.time()
print(99999 in grande_dict)  # True
print(f"Dicionário: {time.time() - inicio:.6f}s")

# Busca em lista é O(n)
inicio = time.time()
print(99999 in grande_lista)  # True
print(f"Lista: {time.time() - inicio:.6f}s")

Armadilhas comuns a evitar:
- Cópias acidentais: modificar um dicionário copiado superficialmente pode afetar o original em estruturas aninhadas
- Chaves duplicadas: ao criar um dicionário, chaves repetidas sobrescrevem valores anteriores
- Mutabilidade interna: valores mutáveis (listas, dicionários) dentro de um dicionário podem causar efeitos colaterais

Os dicionários são fundamentais para programação Python eficiente. Dominá-los significa ter uma ferramenta poderosa para organizar, acessar e manipular dados com clareza e performance.

Referências