O módulo collections
Nesta aula, exploramos o módulo collections do Python, que fornece tipos de dados especializados. Abordamos defaultdict, Counter, deque e namedtuple, com exemplos práticos e boas práticas de uso.
O módulo collections é uma das bibliotecas mais úteis do Python, oferecendo estruturas de dados que estendem os tipos nativos para resolver problemas comuns de forma mais eficiente e elegante. Nesta aula, vamos mergulhar em quatro dessas ferramentas: defaultdict, Counter, deque e namedtuple. Cada uma delas atende a necessidades específicas, como contagem de elementos, filas eficientes, dicionários com valores padrão e tuplas com campos nomeados.
Dominar esses tipos não só torna seu código mais limpo, como também melhora a performance em muitas situações. Vamos ver cada um com exemplos práticos e dicas de uso.
defaultdict
O defaultdict é uma subclasse de dict que sobrescreve um método para fornecer valores padrão quando uma chave não existe. Em um dicionário comum, acessar uma chave inexistente levanta KeyError. Com defaultdict, você pode definir um tipo padrão (como int, list, set), e ele automaticamente cria o valor padrão para a chave faltante.
Isso é extremamente útil para agrupar dados ou contar itens sem precisar verificar se a chave existe. Por exemplo, ao agrupar palavras por letra inicial, você pode usar defaultdict(list) e simplesmente fazer d[letra].append(palavra).
from collections import defaultdict
# Agrupar palavras pela primeira letra
palavras = ['banana', 'abacaxi', 'maçã', 'morango', 'ameixa']
agrupadas = defaultdict(list)
for p in palavras:
agrupadas[p[0]].append(p)
print(dict(agrupadas))
# Saída: {'b': ['banana'], 'a': ['abacaxi', 'ameixa'], 'm': ['maçã', 'morango']}
# Contar ocorrências (similar a Counter, mas manual)
contagem = defaultdict(int)
for p in palavras:
contagem[p[0]] += 1
print(dict(contagem))
# Saída: {'b': 1, 'a': 2, 'm': 2}
Counter
O Counter é uma subclasse de dict projetada para contar objetos hasháveis. Ele armazena elementos como chaves e suas contagens como valores. É ideal para tarefas como contar frequências de letras, palavras ou qualquer item em uma coleção.
Além de contar, o Counter oferece métodos úteis como most_common(), que retorna os elementos mais frequentes, e operações aritméticas entre contadores (união, interseção, etc.).
from collections import Counter
# Contar letras em uma string
frase = "abracadabra"
contagem = Counter(frase)
print(contagem)
# Saída: Counter({'a': 5, 'b': 2, 'r': 2, 'c': 1, 'd': 1})
# Dois mais comuns
print(contagem.most_common(2))
# Saída: [('a', 5), ('b', 2)]
# Operações entre contadores
c1 = Counter(a=3, b=1)
c2 = Counter(a=1, b=2)
print(c1 + c2) # Soma: Counter({'a': 4, 'b': 3})
print(c1 & c2) # Interseção (mínimo): Counter({'a': 1, 'b': 1})
deque
O deque (double-ended queue) é uma lista otimizada para inserções e remoções rápidas em ambas as extremidades. Enquanto listas do Python têm custo O(n) para inserir ou remover no início, o deque faz isso em O(1). É ideal para filas, pilhas e buffers circulares.
O deque suporta métodos como append, appendleft, pop, popleft, e também permite rotação com rotate e limitar o tamanho máximo com maxlen.
from collections import deque
# Fila (FIFO)
fila = deque()
fila.append(1)
fila.append(2)
fila.append(3)
print(fila.popleft()) # 1
print(fila) # deque([2, 3])
# Pilha (LIFO)
pilha = deque()
pilha.append(1)
pilha.append(2)
print(pilha.pop()) # 2
# Rotação
d = deque([1, 2, 3, 4])
d.rotate(1) # move um para a direita
print(d) # deque([4, 1, 2, 3])
# Tamanho máximo (buffer circular)
d = deque(maxlen=3)
for i in range(5):
d.append(i)
print(d) # mantém apenas os últimos 3 elementos
namedtuple
O namedtuple é uma fábrica de tuplas que cria classes de tuplas com campos nomeados. Elas são imutáveis como tuplas, mas oferecem acesso por nome, o que torna o código mais legível e autodocumentado. São leves e consomem menos memória que dicionários.
Para criar uma namedtuple, você fornece um nome para a classe e uma lista de nomes de campos. Os objetos resultantes podem ser acessados por índice ou por nome, e também são desempacotáveis.
from collections import namedtuple
# Definir um tipo Ponto
Ponto = namedtuple('Ponto', ['x', 'y'])
p1 = Ponto(3, 4)
print(p1.x, p1.y) # 3 4
print(p1[0], p1[1]) # 3 4
# Desempacotar
x, y = p1
print(x, y) # 3 4
# Métodos úteis
print(p1._asdict()) # OrderedDict([('x', 3), ('y', 4)])
print(p1._replace(x=10)) # Ponto(x=10, y=4)
# Exemplo prático: representar um registro
Aluno = namedtuple('Aluno', ['nome', 'nota'])
alunos = [Aluno('Ana', 9.5), Aluno('João', 8.0)]
for a in alunos:
print(f"{a.nome}: {a.nota}")
Boas práticas
Ao usar defaultdict, lembre-se de que a função fábrica é chamada sem argumentos para criar valores padrão. Evite usá-lo se você precisar de um comportamento silencioso para chaves inexistentes, pois isso pode esconder bugs. Prefira Counter para contagens, pois é mais expressivo. Use deque quando precisar de operações rápidas nas pontas; para acesso aleatório frequente, uma lista é melhor. namedtuple é excelente para dados imutáveis e leves; se precisar de mutabilidade, considere dataclasses ou dicionários.
Referências
- Documentação oficial do módulo collections
- defaultdict - documentação
- Counter - documentação
- deque - documentação
- namedtuple - documentação
- Real Python: Using defaultdict
- Real Python: Counter
Exercícios
-
Usando
defaultdict, escreva uma função que receba uma lista de números e retorne um dicionário onde as chaves são os números e os valores são listas dos índices em que cada número aparece. Por exemplo,indices([1, 2, 1, 3])deve retornar{1: [0, 2], 2: [1], 3: [3]}.✓ Resposta:from collections import defaultdict def indices(lista): d = defaultdict(list) for i, valor in enumerate(lista): d[valor].append(i) return dict(d) print(indices([1, 2, 1, 3])) # {1: [0, 2], 2: [1], 3: [3]} -
Dada uma string, use
Counterpara encontrar o caractere mais frequente. Se houver empate, retorne qualquer um.✓ Resposta:from collections import Counter def mais_frequente(s): contagem = Counter(s) return contagem.most_common(1)[0][0] print(mais_frequente("abracadabra")) # 'a' -
Implemente uma fila usando
dequeque tenha capacidade máxima de 5. Adicione 7 elementos e mostre o estado final da fila.✓ Resposta:from collections import deque fila = deque(maxlen=5) for i in range(7): fila.append(i) print(fila) # Mostra a fila a cada inserção # Estado final: deque([2, 3, 4, 5, 6]) -
Crie uma
namedtuplechamadaLivrocom campostitulo,autoreano. Instancie dois livros e crie uma lista. Depois, imprima os títulos dos livros publicados após 2000.✓ Resposta:from collections import namedtuple Livro = namedtuple('Livro', ['titulo', 'autor', 'ano']) l1 = Livro('1984', 'George Orwell', 1949) l2 = Livro('O Código Da Vinci', 'Dan Brown', 2003) l3 = Livro('A Guerra dos Tronos', 'George R.R. Martin', 1996) l4 = Livro('O Nome do Vento', 'Patrick Rothfuss', 2007) livros = [l1, l2, l3, l4] for livro in livros: if livro.ano > 2000: print(livro.titulo) # Saída: O Código Da Vinci, O Nome do Vento -
Combine
defaultdictedequepara implementar um sistema de filas por prioridade. Crie um dicionário onde cada chave é uma prioridade (1, 2, 3) e o valor é um deque de tarefas. Adicione tarefas a diferentes prioridades e depois remova uma tarefa da prioridade mais alta (menor número) que não esteja vazia.✓ Resposta:from collections import defaultdict, deque # Sistema de filas por prioridade filas = defaultdict(deque) # Adicionar tarefas filas[1].append("Tarefa urgente") filas[2].append("Tarefa normal") filas[1].append("Outra urgente") filas[3].append("Tarefa baixa") # Remover da maior prioridade (menor número) não vazia for prioridade in sorted(filas): if filas[prioridade]: tarefa = filas[prioridade].popleft() print(f"Executando: {tarefa} (prioridade {prioridade})") break # Saída: Executando: Tarefa urgente (prioridade 1)