O módulo collections da biblioteca padrão do Python é um dos conjuntos de ferramentas mais valiosos para qualquer desenvolvedor. Ele oferece tipos de dados especializados que vão além dos tipos nativos como listas, tuplas e dicionários, resolvendo problemas comuns de forma elegante e eficiente. De acordo com a documentação oficial do Python para collections, este módulo implementa containers de dados alternativos com características específicas para diferentes cenários.
Se você já precisou contar elementos de uma lista, criar uma fila eficiente ou acessar múltiplos dicionários como se fossem um só, o módulo collections tem a solução ideal. Ele complementa perfeitamente os dicionários em Python e as tuplas e conjuntos em Python, que são estruturas fundamentais da linguagem. Neste guia completo, você aprenderá cada um dos principais componentes do módulo collections com exemplos práticos e casos de uso reais.
O que é o módulo collections?
O módulo collections foi introduzido na versão 2.4 do Python e expandido significativamente ao longo dos anos. Ele fornece alternativas aos tipos de dados nativos para situações onde listas, tuplas e dicionários comuns não são suficientes ou não são eficientes. Cada classe do módulo resolve um problema específico: manter a ordem de inserção, fornecer valores padrão para chaves ausentes, criar objetos leves similares a tuplas com campos nomeados, entre outros.
A documentação da Real Python sobre collections oferece uma excelente introdução aos conceitos fundamentais, classificando cada estrutura de dados de acordo com seu caso de uso ideal. O módulo é puramente implementado em Python, com otimizações em C para máxima performance, como você pode conferir no código-fonte oficial do CPython no GitHub.
Counter: contando elementos como um profissional
A classe Counter é uma subclasse de dict projetada especificamente para contar objetos hasháveis. Ela armazena os elementos como chaves e suas contagens como valores, tornando trivial responder perguntas como "qual palavra aparece com mais frequência neste texto?" ou "quantas vezes este número aparece na lista?".
Criando e usando um Counter
from collections import Counter
A partir de uma lista
frutas = ['maçã', 'banana', 'maçã', 'laranja', 'banana', 'maçã']
contador = Counter(frutas)
print(contador)
Counter({'maçã': 3, 'banana': 2, 'laranja': 1})
A partir de uma string
letras = Counter("paralelepipedo")
print(letras)
Counter({'p': 3, 'e': 3, 'a': 2, 'l': 2, 'r': 1, 'i': 1, 'd': 1, 'o': 1})
A partir de um dicionário
contador = Counter({'a': 4, 'b': 2, 'c': 1})
Métodos essenciais do Counter
O Counter oferece métodos que vão muito além da simples contagem. O método most_common() retorna os n elementos mais frequentes, ideal para rankings:
from collections import Counter
vendas = Counter(['iphone', 'iphone', 'samsung', 'iphone', 'xiaomi', 'samsung'])
print(vendas.most_common(2))
[('iphone', 3), ('samsung', 2)]
Operações aritméticas entre Counters são suportadas naturalmente:
c1 = Counter(a=3, b=1, c=2)
c2 = Counter(a=1, b=2, c=3)
print(c1 + c2) # Soma: Counter({'c': 5, 'a': 4, 'b': 3})
print(c1 - c2) # Subtração: Counter({'a': 2}) (apenas positivos)
print(c1 & c2) # Interseção (mínimo): Counter({'a': 1, 'b': 1, 'c': 2})
print(c1 | c2) # União (máximo): Counter({'c': 3, 'a': 3, 'b': 2})
A classe Counter é amplamente utilizada em processamento de linguagem natural, análise de logs, e qualquer cenário que exija contagem de frequência. Consulte a documentação oficial sobre objetos Counter para detalhes completos da API.
defaultdict: dicionários com valores padrão
Quantas vezes você escreveu um código como este?
dicionario = {}
for chave, valor in dados:
if chave not in dicionario:
dicionario[chave] = []
dicionario[chave].append(valor)
O defaultdict elimina essa repetição. Ele é uma subclasse de dict que chama uma função factory para fornecer valores padrão quando uma chave inexistente é acessada:
from collections import defaultdict
defaultdict com list como factory
dados = [('a', 1), ('b', 2), ('a', 3), ('c', 4), ('b', 5)]
dd = defaultdict(list)
for chave, valor in dados:
dd[chave].append(valor)
print(dict(dd))
{'a': [1, 3], 'b': [2, 5], 'c': [4]}
Usos comuns do defaultdict
As factories mais utilizadas com defaultdict são list, set, int e dict:
from collections import defaultdict
defaultdict(int) para contagem automática
contagem = defaultdict(int)
for palavra in ["um", "dois", "um", "tres", "um", "dois"]:
contagem[palavra] += 1
print(dict(contagem))
{'um': 3, 'dois': 2, 'tres': 1}
defaultdict(set) para conjuntos
agrupamento = defaultdict(set)
agrupamento['pares'].add(2)
agrupamento['pares'].add(4)
agrupamento['impares'].add(1)
print(dict(agrupamento))
{'pares': {2, 4}, 'impares': {1}}
defaultdict(dict) para dicionários aninhados
aninhado = defaultdict(dict)
aninhado['user1']['nome'] = 'Alice'
aninhado['user2']['nome'] = 'Bob'
print(dict(aninhado))
{'user1': {'nome': 'Alice'}, 'user2': {'nome': 'Bob'}}
O defaultdict é especialmente útil ao processar dados agrupados e construir estruturas aninhadas. Veja a documentação oficial sobre defaultdict para mais exemplos e casos de uso avançados.
namedtuple: tuplas com nomes de campos
A função namedtuple() cria classes de tupla cujos campos podem ser acessados tanto por índice quanto por nome. Isso combina a imutabilidade e eficiência das tuplas com a legibilidade dos dicionários:
from collections import namedtuple
Definindo uma namedtuple
Ponto = namedtuple('Ponto', ['x', 'y'])
p1 = Ponto(10, 20)
p2 = Ponto(x=30, y=40)
print(p1.x, p1.y) # 10 20 (acesso por nome)
print(p2[0], p2[1]) # 30 40 (acesso por índice)
Sua representação é limpa
print(p1) # Ponto(x=10, y=20)
Namedtuples em aplicações reais
Namedtuples são ideais para representar registros leves sem a sobrecarga de uma classe completa. Elas são imutáveis, consomem menos memória que dicionários e são mais legíveis que tuplas comuns:
from collections import namedtuple
Registro de funcionário
Funcionario = namedtuple('Funcionario', ['id', 'nome', 'cargo', 'salario'])
funcs = [
Funcionario(1, 'Ana Silva', 'Engenheira de Dados', 12000),
Funcionario(2, 'Carlos Souza', 'Cientista de Dados', 15000),
Funcionario(3, 'Maria Lima', 'Desenvolvedora Python', 10000),
]
Filtrar com comprensão de lista
engenheiros = [f for f in funcs if 'Dados' in f.cargo]
print(engenheiros[0].nome) # Ana Silva
Além dos campos nomeados, a namedtuple oferece o método _asdict() para converter em dicionário e _replace() para criar uma nova instância com campos alterados:
p = Ponto(10, 20)
print(p._asdict()) # {'x': 10, 'y': 20}
p2 = p._replace(x=50)
print(p2) # Ponto(x=50, y=20)
Segundo a documentação oficial sobre namedtuple, esta função é especialmente útil para substituir tuplas comuns quando a legibilidade do código é importante.
deque: filas e pilhas eficientes
A classe deque (double-ended queue) é uma lista otimizada para inserções e remoções em ambas as extremidades. Enquanto uma lista Python tem complexidade O(n) para inserir ou remover no início, o deque oferece O(1) para essas operações:
from collections import deque
Criando um deque
fila = deque(['a', 'b', 'c'])
Adicionar no final
fila.append('d')
print(fila) # deque(['a', 'b', 'c', 'd'])
Adicionar no início
fila.appendleft('z')
print(fila) # deque(['z', 'a', 'b', 'c', 'd'])
Remover do final
ultimo = fila.pop()
print(ultimo) # d
Remover do início
primeiro = fila.popleft()
print(primeiro) # z
Rotacionando e limitando o deque
O deque oferece duas funcionalidades poderosas: rotação e tamanho máximo:
from collections import deque
Rotação (útil para jogos e algoritmos circulares)
d = deque([1, 2, 3, 4, 5])
d.rotate(2)
print(d) # deque([4, 5, 1, 2, 3])
d.rotate(-1)
print(d) # deque([5, 1, 2, 3, 4])
Tamanho máximo (buffer circular automático)
buffer = deque(maxlen=3)
buffer.append(1)
buffer.append(2)
buffer.append(3)
print(buffer) # deque([1, 2, 3], maxlen=3)
buffer.append(4) # Remove automaticamente o elemento mais antigo
print(buffer) # deque([2, 3, 4], maxlen=3)
O deque é a estrutura ideal para implementar filas de tarefas, histórico de navegação (com maxlen), buffers circulares e algoritmos de sliding window. Consulte a documentação oficial sobre objetos deque para uma visão completa de todos os métodos disponíveis.
OrderedDict: dicionários com ordem garantida
Antes do Python 3.7, os dicionários comuns não garantiam ordem de inserção. O OrderedDict foi criado para preencher essa lacuna. Hoje, com dicionários nativos ordenados, seu principal diferencial é o método move_to_end():
from collections import OrderedDict
od = OrderedDict()
od['a'] = 1
od['b'] = 2
od['c'] = 3
od['d'] = 4
Move a chave 'a' para o final
od.move_to_end('a')
print(od)
OrderedDict([('b', 2), ('c', 3), ('d', 4), ('a', 1)])
Move 'd' para o início
od.move_to_end('d', last=False)
print(od)
OrderedDict([('d', 4), ('b', 2), ('c', 3), ('a', 1)])
Além disso, o OrderedDict considera a ordem ao comparar igualdade, ao contrário dos dicionários comuns:
from collections import OrderedDict
od1 = OrderedDict([('a', 1), ('b', 2)])
od2 = OrderedDict([('b', 2), ('a', 1)])
print(od1 == od2) # False (a ordem importa!)
dict1 = {'a': 1, 'b': 2}
dict2 = {'b': 2, 'a': 1}
print(dict1 == dict2) # True (ordem não importa)
O OrderedDict é ideal para implementar caches LRU (Least Recently Used) e qualquer estrutura que precise manter registro de ordem de acesso ou inserção. Veja a documentação oficial sobre OrderedDict para mais detalhes e exemplos.
ChainMap: múltiplos dicionários em um só
A classe ChainMap agrupa múltiplos dicionários ou mapeamentos em uma única visão pesquisável. As buscas percorrem os dicionários na ordem em que foram passados, retornando o primeiro valor encontrado:
from collections import ChainMap
Configurações com precedência
padroes = {'tema': 'claro', 'idioma': 'pt-BR', 'notificacoes': True}
usuario = {'idioma': 'en-US', 'notificacoes': False}
ambiente = {'tema': 'escuro'}
config = ChainMap(ambiente, usuario, padroes)
print(config['tema']) # 'escuro' (vem de ambiente)
print(config['idioma']) # 'en-US' (vem de usuario)
print(config['notificacoes']) # False (vem de usuario)
Atualizações afetam apenas o primeiro dicionário
config['tema'] = 'contraste'
print(ambiente['tema']) # 'contraste'
O ChainMap é extremamente útil para gerenciar configurações com diferentes níveis de precedência (padrão → usuário → ambiente), processamento de argumentos de linha de comando combinados com defaults, e escopos de variáveis em interpretadores:
from collections import ChainMap
Escopo de variáveis simulando um interpretador
global_scope = {'x': 10, 'y': 20, 'nome': 'global'}
local_scope = {'x': 5, 'z': 30}
scope = ChainMap(local_scope, global_scope)
print(scope['x']) # 5 (local)
print(scope['y']) # 20 (global)
print(scope['z']) # 30 (local)
Adicionar novo escopo
scope = scope.new_child({'x': 1, 'w': 100})
print(scope['x']) # 1 (novo escopo local)
De acordo com a documentação oficial sobre ChainMap, esta classe é particularmente útil quando você precisa gerenciar múltiplos namespaces sem mesclá-los.
Outras ferramentas do módulo collections
Além das classes principais, o módulo collections oferece outras ferramentas valiosas:
UserDict, UserList e UserString
Estas classes são wrappers que facilitam a criação de subclasses de dict, list e string. Diferente de herdar diretamente desses tipos nativos, essas classes expõem atributos como .data para acesso ao conteúdo interno, simplificando a personalização:
from collections import UserDict
class DicionarioMinusculo(UserDict):
def setitem(self, chave, valor):
chave = chave.lower()
super().setitem(chave, valor)
d = DicionarioMinusculo()
d['Nome'] = 'Alice'
print(d.data) # {'nome': 'Alice'}
Boas práticas com collections
Para tirar o máximo proveito do módulo collections, considere as seguintes recomendações:
- Use Counter em vez de implementar sua própria lógica de contagem com dicionários — o código fica mais legível e eficiente
- Prefira defaultdict sempre que precisar verificar se uma chave existe antes de acessá-la; isso elimina blocos
if chave in dictrepetitivos - Escolha namedtuple sobre tuplas comuns quando os dados têm significado semântico; seu código se torna autodocumentado
- Utilize deque para filas e pilhas em vez de listas quando houver operações frequentes no início da coleção
- Recorra a OrderedDict quando a ordem de inserção importar para a lógica do seu algoritmo
- Adote ChainMap para gerenciar configurações com múltiplas camadas de precedência sem mesclar dicionários
Performance: collections vs tipos nativos
Um aspecto frequentemente subestimado é o ganho de performance ao usar as classes certas do módulo collections. Veja uma comparação prática:
from collections import deque, Counter, defaultdict
import time
deque vs list para inserção no início
n = 100000
lista = []
inicio = time.time()
for i in range(n):
lista.insert(0, i)
print(f"list.insert(0): {time.time() - inicio:.3f}s")
deq = deque()
inicio = time.time()
for i in range(n):
deq.appendleft(i)
print(f"deque.appendleft: {time.time() - inicio:.3f}s")
Resultado típico: deque é 100x mais rápido
O módulo collections implementa cada classe com a estrutura de dados mais adequada para seu propósito. O deque, por exemplo, é implementado como um array de blocos fixos (double-ended queue), enquanto o Counter herda a implementação otimizada em C do dict nativo. Para benchmarks detalhados, consulte o guia de performance da documentação oficial.
Conclusão
O módulo collections é uma das bibliotecas mais úteis da stdlib do Python. Ele fornece soluções elegantes e eficientes para problemas recorrentes de programação: contagem de frequência com Counter, valores padrão com defaultdict, registros nomeados com namedtuple, filas eficientes com deque, ordem garantida com OrderedDict e escopos encadeados com ChainMap.
Dominar essas ferramentas não apenas torna seu código mais limpo e legível, mas também melhora significativamente a performance das suas aplicações. Cada classe foi projetada para um conjunto específico de problemas, e saber qual escolher é uma marca de um desenvolvedor Python experiente.
Para continuar seus estudos, explore a documentação oficial completa do módulo collections e pratique implementando os exemplos deste guia em seus próprios projetos. O conhecimento aprofundado das ferramentas padrão do Python é um dos maiores diferenciais de um programador eficiente.