Módulo collections do Python
Aprenda o módulo collections do Python: Counter, defaultdict, namedtuple, deque, OrderedDict e ChainMap — com exemplos práticos e quando usar cada um.
O módulo collections embutido do Python fornece tipos de contêiner especializados que estendem ou substituem as estruturas padrão list, dict e tuple. Cada tipo resolve um problema específico que, de outra forma, exigiria várias linhas extras de código manual.
Este capítulo abrange os seis tipos mais utilizados: Counter, defaultdict, namedtuple, deque, OrderedDict e ChainMap. Para cada um, você verá qual problema ele resolve, como criá-lo e usá-lo, e os pontos de atenção a observar.
Nenhuma instalação é necessária — collections vem incluído em toda instalação do Python 3:
from collections import Counter, defaultdict, namedtuple, deque, OrderedDict, ChainMapCounter
Counter é uma subclasse de dict projetada para contar objetos hasheáveis. Você fornece um iterável (ou uma string, ou argumentos nomeados) e ele retorna um objeto semelhante a um dicionário onde as chaves são os elementos e os valores são suas contagens.
Criando um Counter
from collections import Counter
# From a list
word_list = ['apple', 'banana', 'apple', 'cherry', 'banana', 'apple']
c = Counter(word_list)
print(c)
# Output: Counter({'apple': 3, 'banana': 2, 'cherry': 1})Chaves ausentes retornam 0 em vez de lançar KeyError:
print(c['apple']) # 3
print(c['mango']) # 0 — no KeyErrorElementos mais comuns
most_common(n) retorna os n elementos com maior contagem como uma lista de tuplas (elemento, contagem), ordenados do mais ao menos frequente:
print(c.most_common(2))
# Output: [('apple', 3), ('banana', 2)]Omita n para obter todos os elementos ordenados por frequência.
Aritmética de Counter
Counters suportam adição, subtração, interseção e união:
a = Counter(['a', 'a', 'b']) # Counter({'a': 2, 'b': 1})
b = Counter(['a', 'b', 'b', 'c']) # Counter({'b': 2, 'a': 1, 'c': 1})
print(a + b) # Counter({'a': 3, 'b': 3, 'c': 1})
print(a - b) # Counter({'a': 1}) — only positive counts kept
print(a & b) # Counter({'a': 1, 'b': 1}) — minimum of each count
print(a | b) # Counter({'a': 2, 'b': 2, 'c': 1}) — maximum of each countQuando usar Counter: contagem de votos, frequência de palavras, contagem de caracteres, construção de histogramas.
Armadilha do Counter: subtração mantém apenas positivos
a - b descarta silenciosamente os elementos cujo resultado seria zero ou negativo. Se você precisa manter contagens negativas, use subtract() em vez disso:
a = Counter({'x': 2})
b = Counter({'x': 5})
a.subtract(b)
print(a) # Counter({'x': -3}) — negative count preserveddefaultdict
defaultdict é uma subclasse de dict que chama uma função de fábrica para fornecer um valor padrão sempre que você acessa uma chave que ainda não existe. Isso elimina a necessidade de cláusulas de guarda if key not in d:.
Criando um defaultdict
Passe a fábrica como primeiro argumento:
from collections import defaultdict
dd = defaultdict(int) # default value: int() == 0
words = ['cat', 'dog', 'cat', 'bird', 'dog', 'cat']
for word in words:
dd[word] += 1 # no KeyError on first access
print(dict(dd))
# Output: {'cat': 3, 'dog': 2, 'bird': 1}Sem defaultdict, você precisaria usar dd[word] = dd.get(word, 0) + 1 ou um Counter.
Agrupando itens com list como fábrica
groups = defaultdict(list)
data = [('fruit', 'apple'), ('veggie', 'carrot'), ('fruit', 'banana'), ('veggie', 'broccoli')]
for category, item in data:
groups[category].append(item)
print(dict(groups))
# Output: {'fruit': ['apple', 'banana'], 'veggie': ['carrot', 'broccoli']}Funções de fábrica comuns
| Fábrica | Valor padrão | Uso típico |
|---|---|---|
int | 0 | Contagem |
float | 0.0 | Acumulação de somas |
list | [] | Agrupamento de itens |
set | set() | Coleta de valores únicos |
str | '' | Construção de strings |
dict | {} | Mapeamentos aninhados |
Você também pode passar uma lambda sem argumentos para um padrão personalizado: defaultdict(lambda: 'N/A').
Armadilha do defaultdict: acessar uma chave a cria
Ao contrário de dict.get(), um simples acesso dd[key] a uma chave inexistente insere essa chave com o valor padrão. Isso pode surpreender ao iterar ou verificar a existência de membros:
dd = defaultdict(int)
print('foo' in dd) # False — key does not exist yet
_ = dd['foo'] # access inserts the key
print('foo' in dd) # True — key was silently createdUse dd.get('foo') ou 'foo' in dd quando quiser verificar sem efeitos colaterais.
Quando usar defaultdict: agrupamento de dados, construção de listas de adjacência para grafos, qualquer padrão em que você inicializa e depois atualiza.
namedtuple
namedtuple cria uma nova classe cujas instâncias são como tuplas comuns, mas com campos nomeados. O resultado é imutável, eficiente em memória (sem __dict__ por instância) e autodocumentado.
Criando um namedtuple
from collections import namedtuple
Point = namedtuple('Point', ['x', 'y'])
p = Point(3, 7)
print(p) # Point(x=3, y=7)
print(p.x) # 3
print(p.y) # 7
print(p[0]) # 3 — index access still worksO primeiro argumento de namedtuple() é o nome do tipo (usado no repr). O segundo argumento é uma lista de nomes de campos (ou uma string separada por espaço/vírgula: 'x y').
Exemplo prático
Employee = namedtuple('Employee', ['name', 'department', 'salary'])
emp = Employee('Alice', 'Engineering', 95000)
print(emp.name, emp.department, emp.salary)
# Output: Alice Engineering 95000O acesso por nome (emp.name) é muito mais claro do que o acesso posicional (row[0]) ao ler dados de arquivos CSV ou linhas de banco de dados.
Métodos úteis do namedtuple
# Convert to an ordered dictionary
print(p._asdict()) # {'x': 3, 'y': 7}
# Create a modified copy (namedtuples are immutable)
p2 = p._replace(x=10)
print(p2) # Point(x=10, y=7)
print(p) # Point(x=3, y=7) — original unchangednamedtuple vs dataclass
O Python 3.7 introduziu dataclasses.dataclass como alternativa. Escolha namedtuple quando quiser imutabilidade e compatibilidade total com tuplas (desempacotamento, indexação, hashing). Escolha dataclass quando precisar de campos mutáveis, fábricas de padrões ou métodos.
Quando usar namedtuple: representar registros (linhas de banco de dados, linhas de CSV, pares de coordenadas, cores RGB) onde imutabilidade e baixo uso de memória importam.
deque
deque (fila de dupla extremidade, pronunciado "dek") é uma sequência otimizada para adições e remoções O(1) em ambas as extremidades. Uma lista comum alcança O(1) com append e O(n) com insert(0, …); deque alcança O(1) em ambas as extremidades.
Criando um deque
from collections import deque
d = deque([1, 2, 3])
print(d) # deque([1, 2, 3])Adicionando e removendo elementos
d.append(4) # add to right
d.appendleft(0) # add to left
print(d) # deque([0, 1, 2, 3, 4])
d.pop() # remove from right → 4
d.popleft() # remove from left → 0
print(d) # deque([1, 2, 3])Rotacionando um deque
rotate(n) desloca os elementos para a direita em n posições (negativo = esquerda):
d = deque([1, 2, 3])
d.rotate(1)
print(d) # deque([3, 1, 2])
d.rotate(-1)
print(d) # deque([1, 2, 3])deque com limite (janela deslizante / buffer FIFO)
Definir maxlen limita o tamanho do deque. Quando novos itens são adicionados além do limite, itens caem automaticamente da extremidade oposta — perfeito para manter os últimos N eventos:
buffer = deque(maxlen=3)
for i in range(5):
buffer.append(i)
print(buffer) # deque([2, 3, 4], maxlen=3)Armadilha do deque: acesso aleatório lento O(n)
deque não suporta acesso aleatório eficiente. d[500] é O(n), não O(1) como em uma lista. Se você frequentemente indexa por posição, use uma lista. Use deque apenas quando precisar de adições e remoções rápidas em ambas as extremidades.
Quando usar deque: implementar filas e pilhas, algoritmos de janela deslizante, busca em largura, armazenar as últimas N entradas de log.
OrderedDict
Desde o Python 3.7, o dict comum mantém a ordem de inserção. Então por que usar OrderedDict?
Dois motivos ainda são relevantes:
move_to_end()— permite reordenar chaves eficientemente para o início ou o fim.- Igualdade — duas instâncias de
OrderedDictcom as mesmas chaves mas em ordens de inserção diferentes são comparadas como não iguais, ao contrário de dicts comuns.
Criando e reordenando um OrderedDict
from collections import OrderedDict
od = OrderedDict()
od['one'] = 1
od['two'] = 2
od['three'] = 3
print(list(od.keys())) # ['one', 'two', 'three']
od.move_to_end('one') # move 'one' to the end
print(list(od.keys())) # ['two', 'three', 'one']
od.move_to_end('three', last=False) # move 'three' to the front
print(list(od.keys())) # ['three', 'two', 'one']Igualdade sensível à ordem
od1 = OrderedDict([('a', 1), ('b', 2)])
od2 = OrderedDict([('b', 2), ('a', 1)])
print(od1 == od2) # False — different order
d1 = {'a': 1, 'b': 2}
d2 = {'b': 2, 'a': 1}
print(d1 == d2) # True — regular dicts ignore orderQuando usar OrderedDict: implementações de cache LRU (mover a chave usada recentemente para o fim), qualquer algoritmo onde a ordem de inserção deve fazer parte da igualdade.
ChainMap
ChainMap agrupa vários dicionários em uma única visão lógica. As buscas percorrem os mapas em ordem; escritas e exclusões afetam apenas o primeiro mapa.
Uso básico
from collections import ChainMap
defaults = {'color': 'blue', 'size': 'medium', 'theme': 'light'}
overrides = {'color': 'red', 'size': 'large'}
combined = ChainMap(overrides, defaults)
print(combined['color']) # 'red' — found in overrides first
print(combined['theme']) # 'light' — not in overrides, falls back to defaultsAs escritas vão apenas para o primeiro mapa:
combined['font'] = 'serif'
print(overrides) # {'color': 'red', 'size': 'large', 'font': 'serif'}
print(defaults) # {'color': 'blue', 'size': 'medium', 'theme': 'light'} — unchangedSimulando escopos de variáveis com new_child()
base = ChainMap({'x': 1})
child = base.new_child({'x': 99, 'y': 2})
print(child['x']) # 99 — child scope shadows parent
print(child['y']) # 2
print(child.parents['x']) # 1 — access parent scope directlynew_child() retorna um novo ChainMap com um dict vazio adicionado no início, que é como as próprias regras de escopo do Python (local → envolvente → global → embutido) são modeladas internamente.
Quando usar ChainMap: camadas de configuração (substituições do usuário → padrões do projeto → padrões globais), implementação de ambientes com escopo, combinação de argumentos de linha de comando com variáveis de ambiente e arquivos de configuração.
Escolhendo o Tipo Certo
| Você precisa… | Use |
|---|---|
| Contar ocorrências de itens | Counter |
Evitar KeyError com um valor padrão | defaultdict |
| Representar um registro com campos nomeados | namedtuple |
| Adições/remoções rápidas em ambas as extremidades, ou um buffer delimitado | deque |
Igualdade de dict sensível à ordem, ou move_to_end() | OrderedDict |
| Mesclar vários dicts em uma visão sem copiar | ChainMap |
Para mais informações sobre os tipos base que esses estendem, veja Python Dictionaries, Python Lists e Python Tuples. Para auxiliares baseados em iteradores na biblioteca padrão, veja o Módulo itertools do Python.