---
title: "Módulo collections do Python: Counter, defaultdict, deque, namedtuple e mais"
url: "https://python.dev.br/blog/collections-python-guia-completo/"
markdown_url: "https://python.dev.br/blog/collections-python-guia-completo.MD"
description: "Domine o módulo collections do Python com exemplos práticos em português: Counter, defaultdict, deque, namedtuple, OrderedDict e ChainMap para código mais limpo e eficiente."
date: "2026-06-21"
author: "Equipe python.dev.br"
---

# Módulo collections do Python: Counter, defaultdict, deque, namedtuple e mais

Domine o módulo collections do Python com exemplos práticos em português: Counter, defaultdict, deque, namedtuple, OrderedDict e ChainMap para código mais limpo e eficiente.


Quem programa em Python costuma viver com listas, dicionários, tuplas e conjuntos no dia a dia. Eles resolvem a maioria dos problemas. Mas existe um grupo de estruturas especializadas, escondidas na biblioteca padrão, que evitam dezenas de linhas de código repetitivo e bugs sutis: o módulo `collections`. Se você já escreveu um loop só para contar frequências, inicializou um dicionário com listas vazias ou implementou uma fila com `list.pop(0)`, provavelmente estava reinventando o que o `collections` já oferece pronto e testado.

Este guia cobre as principais ferramentas do módulo — `Counter`, `defaultdict`, `deque`, `namedtuple`, `OrderedDict` e `ChainMap` — com exemplos práticos em contexto brasileiro. Ele complementa conteúdos como [Estruturas de Dados em Python](/blog/estruturas-de-dados-python/), [Geradores e Iteradores](/blog/geradores-iteradores-python/), [Dataclasses: Guia Completo](/blog/dataclasses-python-guia-completo/) e [Tipagem Estática com mypy](/blog/tipagem-estatica-python-mypy/). O objetivo não é listar a API inteira, mas mostrar quando cada ferramenta vale a pena e quando é exagero.

## Por que o módulo collections existe

A filosofia do `collections` é simples: oferecer **estruturas de dados para casos comuns que o `dict`, a `list` e a `tuple` não resolvem bem**. Antes de existirem, todo programador Python reimplementava as mesmas soluções — contadores manuais, dicionários com valor padrão, filas lentas com `pop(0)` — cometendo os mesmos erros de performance e os mesmos bugs de borda.

Usar `collections` traz três vantagens concretas:

1. **Código mais curto e legível**: uma linha de `Counter` substitui um loop de cinco linhas.
2. **Performance previsível**: `deque.popleft()` é O(1), enquanto `list.pop(0)` é O(n).
3. **Menos bugs**: `defaultdict(list)` elimina o padrão `if chave not in d: d[chave] = []` que aparece em todo lugar.

O custo é quase zero: tudo fica na biblioteca padrão, sem dependências externas, e funciona em qualquer Python 3.6 ou superior.

## Counter: contagem de frequências

`Counter` é um dicionário especializado em contar objetos. Recebe qualquer iterável e devolve um `dict`-like em que cada chave é um item e o valor é quantas vezes ele apareceu.

### Exemplo básico: palavras mais frequentes

Imagine que você quer descobrir quais linguagens aparecem mais em uma lista de vagas de emprego:

```python
from collections import Counter

vagas = [
    "python", "django", "python", "sql", "python",
    "django", "aws", "python", "sql", "docker",
    "python", "django", "aws", "kafka", "python",
]

contador = Counter(vagas)

# Acessa direto como um dicionário, sem KeyError:
print(contador["python"])  # 6
print(contador["cobol"])   # 0  (ausência vira zero)

# Mais comuns primeiro:
print(contador.most_common(3))
# [('python', 6), ('django', 3), ('sql', 2)]
```

Note um detalhe importante: acessar uma chave inexistente em `Counter` retorna `0`, não levanta `KeyError`. Isso elimina um `try/except` inteiro.

### Operações entre contadores

`Counter` suporta aritmética de conjuntos, o que é muito útil para comparar períodos:

```python
from collections import Counter

vagas_2025 = Counter(python=40, django=20, sql=15, cobol=2)
vagas_2026 = Counter(python=60, django=30, sql=25, rust=10)

# Quais cresceram entre os anos?
crescimento = vagas_2026 - vagas_2025
print(crescimento)
# Counter({'python': 20, 'django': 10, 'sql': 10, 'rust': 10})

# União: pega o máximo de cada chave
uniao = vagas_2025 | vagas_2026
print(uniao["python"])  # 60

# Interseção: pega o mínimo
intersecao = vagas_2025 & vagas_2026
print(intersecao["python"])  # 40
```

### Quando NÃO usar Counter

`Counter` é ideal quando o valor é uma contagem não-negativa. Se você precisa de um agregador mais genérico (somar valores decimais, manter listas, acumular strings), use `defaultdict`, que é mais flexível. Misturar `Counter` com dados que não são contagens gera código confuso.

## defaultdict: dicionários com valor padrão

`defaultdict` resolve o padrão mais repetitivo do Python: criar chaves novas em um dicionário de listas, conjuntos ou inteiros.

### Agrupando itens por chave

Sem `defaultdict`, agrupar vendas por região fica assim:

```python
vendas = [
    ("Sudeste", 1200), ("Sul", 800), ("Sudeste", 1500),
    ("Nordeste", 600), ("Sul", 950), ("Sudeste", 2000),
    ("Norte", 300), ("Nordeste", 700),
]

# Jeito manual, verboso:
por_regiao = {}
for regiao, valor in vendas:
    if regiao not in por_regiao:
        por_regiao[regiao] = []
    por_regiao[regiao].append(valor)
```

Com `defaultdict`, três linhas somem:

```python
from collections import defaultdict

por_regiao = defaultdict(list)
for regiao, valor in vendas:
    por_regiao[regiao].append(valor)

# Resultado é um dict comum — pode converter se preferir:
dict(por_regiao)
# {'Sudeste': [1200, 1500, 2000],
#  'Sul': [800, 950],
#  'Nordeste': [600, 700],
#  'Norte': [300]}
```

### Factories comuns

A "fábrica" (primeiro argumento) define qual valor padrão será criado. As mais usadas:

```python
defaultdict(list)       # lista vazia []
defaultdict(set)        # conjunto vazio set()
defaultdict(int)        # inteiro 0 (bom para contar)
defaultdict(float)      # 0.0
defaultdict(dict)       # dict vazio {}
```

### Contando com defaultdict(int)

Apesar de `Counter` ser o ideal para contagem pura, `defaultdict(int)` serve quando você quer acumular valores maiores que 1 por ocorrência:

```python
from collections import defaultdict

faturamento_por_cliente = defaultdict(float)
compras = [
    ("Acme Ltda", 1500.00),
    ("Beta ME", 320.50),
    ("Acme Ltda", 2200.00),
    ("Gamma SA", 980.75),
    ("Beta ME", 110.00),
]
for cliente, valor in compras:
    faturamento_por_cliente[cliente] += valor

print(faturamento_por_cliente["Acme Ltda"])  # 3700.0
```

### Quando NÃO usar defaultdict

`defaultdict` cria chaves implicitamente sempre que você acessa uma chave nova. Isso é ótimo em loops de agregação, mas **perigoso quando você só queria consultar**:

```python
from collections import defaultdict

d = defaultdict(list)
"loja_inexistente" in d           # False — correto
x = d["loja_inexistente"]         # cria [] e insere a chave!
"loja_inexistente" in d           # True agora — efeito colateral
```

Para consultas puras, prefira `dict.get(chave, default)` ou verifique com `in` antes de acessar. Para armazenamento de configuração, o dicionário comum costuma ser mais seguro porque nunca cria chaves por engano.

## deque: filas e pilhas eficientes

A lista do Python é eficiente para operações no final (`append`, `pop`), mas **lenta no início**: remover o primeiro elemento com `pop(0)` desloca todos os outros, custando O(n). Para filas, onde você insere de um lado e remove do outro, use `deque` (pronuncia-se "deck"), que oferece `append` e `popleft` em tempo constante.

### Fila FIFO de processamento

Imagine um worker que consome eventos de um webhook:

```python
from collections import deque

fila = deque()

# Produtor: eventos chegam no final
for evento in ["pedido_criado", "pagamento", "estoque", "nota_fiscal"]:
    fila.append(evento)

# Consumidor: processa do início
while fila:
    evento = fila.popleft()
    print(f"processando {evento}")
```

`deque.popleft()` é O(1); `list.pop(0)` é O(n). Em filas com milhares de itens, a diferença é brutal.

### Tamanho fixo: janela deslizante

`deque(maxlen=N)` cria uma fila de tamanho limitado. Ao inserir além do limite, o item mais antigo é descartado automaticamente. É perfeito para manter apenas os últimos N eventos:

```python
from collections import deque

# Mantém apenas os 5 últimos pedidos por usuário
ultimos_pedidos = {}

def registrar_pedido(usuario, pedido_id):
    if usuario not in ultimos_pedidos:
        ultimos_pedidos[usuario] = deque(maxlen=5)
    ultimos_pedidos[usuario].append(pedido_id)

registrar_pedido("diego", 101)
registrar_pedido("diego", 102)
registrar_pedido("diego", 103)
registrar_pedido("diego", 104)
registrar_pedido("diego", 105)
registrar_pedido("diego", 106)  # descarta o 101

print(list(ultimos_pedidos["diego"]))
# [102, 103, 104, 105, 106]
```

Sem `maxlen`, esse padrão exigiria um `if len(fila) > 5: fila.popleft()` a cada inserção.

### Rotação de elementos

`deque.rotate(n)` desloca os elementos `n` posições para a direita (ou esquerda, se `n` for negativo). É útil para algoritmos de round-robin:

```python
from collections import deque

servidores = deque(["srv-br-1", "srv-br-2", "srv-br-3"])

def proximo_servidor():
    servidor = servidores[0]
    servidores.rotate(-1)  # primeiro vira último
    return servidor

for _ in range(6):
    print(proximo_servidor())
# srv-br-1, srv-br-2, srv-br-3, srv-br-1, srv-br-2, srv-br-3
```

### Quando NÃO usar deque

`deque` é mais lenta que `list` para acesso por índice no meio (`d[500]` precisa percorrer a estrutura). Se você precisa de acesso aleatório frequente, fatiamento ou ordenação, use `list`. Use `deque` só quando o padrão de uso for: inserções/remoções nas duas pontas.

## namedtuple: tuplas com nomes

Tuplas comuns são acessadas por índice (`ponto[0]`, `ponto[1]`), o que torna o código ilegível rapidamente. `namedtuple` cria uma subclasse de tupla em que cada posição tem um nome, mantendo a leveza (sem dicionário interno) e a compatibilidade com desempacotamento.

### Definição e uso básico

```python
from collections import namedtuple

Ponto = namedtuple("Ponto", ["x", "y"])
p = Ponto(3, 4)

print(p.x)          # 3 — acesso por nome
print(p[0])         # 3 — acesso por índice (compatível com tupla)
x, y = p            # desempacotamento funciona
print(x, y)         # 3 4
```

### Exemplo prático: retorno de função

Retornar vários valores em uma tupla comum perde o significado dos campos. Com `namedtuple`, o chamador sabe exatamente o que está recebendo:

```python
from collections import namedtuple

Estatisticas = namedtuple("Estatisticas", ["media", "mediana", "desvio_padrao"])

def calcular_estatisticas(valores):
    n = len(valores)
    media = sum(valores) / n
    ordenados = sorted(valores)
    mediana = ordenados[n // 2] if n % 2 else (ordenados[n // 2 - 1] + ordenados[n // 2]) / 2
    variancia = sum((v - media) ** 2 for v in valores) / n
    return Estatisticas(media, mediana, variancia ** 0.5)

stats = calcular_estatisticas([10, 20, 30, 40, 50])
print(f"Média: {stats.media}")              # 30.0
print(f"Mediana: {stats.mediana}")          # 30
print(f"Desvio padrão: {stats.desvio_padrao:.2f}")  # 14.14
```

### namedtuple vs dataclass

Desde o Python 3.7, [dataclasses](/blog/dataclasses-python-guia-completo/) cobrem muitos usos de `namedtuple` com mais flexibilidade: valores padrão, métodos, mutabilidade opcional, tipagem nativa. Quando escolher cada um?

- **`namedtuple`**: quando o objeto é imutável, leve, precisa ser desempacotável como tupla e consumir pouca memória. Útil para registros em pipelines de dados, chaves de cache, retorno de parsers.
- **`dataclass`**: quando você quer métodos, mutabilidade, valores padrão complexos ou integração profunda com [type hints](/blog/tipagem-estatica-python-mypy/) e mypy.

Como regra prática: comece com `dataclass`. Só troque por `namedtuple` se a imutabilidade estrita, o baixo consumo de memória ou a compatibilidade com código que espera tuplas forem essenciais.

## OrderedDict: dicionários que lembram a ordem de inserção

Desde o Python 3.7, o `dict` comum já preserva a ordem de inserção. Então por que `OrderedDict` ainda existe?

A resposta é que `OrderedDict` oferece **operações adicionais** que o `dict` não tem:

```python
from collections import OrderedDict

od = OrderedDict()
od["primeiro"] = 1
od["segundo"] = 2
od["terceiro"] = 3

# move_to_end: reposiciona uma chave
od.move_to_end("primeiro")  # manda para o final
print(list(od.keys()))  # ['segundo', 'terceiro', 'primeiro']

# popitem: remove do início ou do fim
chave, valor = od.popitem(last=False)  # remove o mais antigo
print(chave)  # segundo
```

Use `OrderedDict` quando você implementa caches LRU (Least Recently Used), filas priorizadas ou qualquer estrutura em que precisa mover/reorganizar chaves explicitamente. Para o uso comum de dicionário, o `dict` nativo basta.

## ChainMap: concatenar dicionários sem copiar

`ChainMap` junta vários mapeamentos e busca em todos na ordem, sem criar um novo dicionário. É eficiente quando você tem configurações em camadas (default → arquivo → variáveis de ambiente).

```python
from collections import ChainMap

config_padrao = {"timeout": 30, "retries": 3, "debug": False}
config_arquivo = {"timeout": 60, "debug": True}
config_env = {"debug": False}  # variável de ambiente sobrepõe tudo

config = ChainMap(config_env, config_arquivo, config_padrao)

print(config["timeout"])  # 60 (vem do arquivo)
print(config["retries"])  # 3 (vem do padrão)
print(config["debug"])    # False (env vence)
```

A vantagem sobre `{**padrao, **arquivo, **env}` é que `ChainMap` não copia os dados: ele apenas mantém referências aos mapeamentos originais. Para configurações grandes ou que mudam em runtime, isso evita custo desnecessário.

### Pegadinha: escrita afeta só o primeiro mapa

`config["novo"] = 1` escreve em `config_env`, não em todos. `ChainMap` é uma visão unificada de leitura, não um dicionário fundido para escrita.

## Padrões e armadilhas comuns

Apesar de úteis, as ferramentas do `collections` têm pegadinhas que aparecem em produção. Vamos cobrir as mais frequentes.

### 1. `defaultdict` criando chaves por engano

Já vimos que acessar uma chave inexistente em `defaultdict` cria ela. Em código que só deveria consultar, isso infla o dicionário silenciosamente:

```python
from collections import defaultdict

estoque = defaultdict(int)
# Bug: "verifica" criando a chave
if estoque["produto_inexistente"] == 0:
    print("sem estoque")
# Agora estoque == {"produto_inexistente": 0}

# Correto:
if "produto_inexistente" not in estoque:
    print("sem estoque")
```

### 2. `Counter` com valores negativos

`Counter` permite subtração que gera valores negativos ou zero, mas `most_common` ignora-os só parcialmente:

```python
from collections import Counter

a = Counter(x=5, y=3)
b = Counter(x=8, y=1)
diff = a - b  # {'y': 2}  — x sumiu porque virou <= 0
print(diff)   # Counter({'y': 2})
```

Se você precisa da diferença matemática (incluindo negativos), use aritmética manual ou `dict`, não `Counter`.

### 3. `deque` não suporta fatiamento

```python
from collections import deque
d = deque(range(10))
d[2:5]  # TypeError: sequence index must be integer, not 'slice'
```

Para operações que precisam de fatiamento, converta para `list` ou use `itertools.islice`.

### 4. `namedtuple` vs sobrescrita acidental

`namedtuple` é imutável, então atribuição falha com `AttributeError`. Mas nomes de campo que colidem com métodos (`count`, `index`) quebram silenciosamente — o Python avisa, mas não impede. Sempre use nomes que não conflitem com métodos de tupla.

## Quando cada ferramenta brilha

Resumo prático para escolher rápido:

| Precisa de... | Use |
|---|---|
| Contar frequência de itens | `Counter` |
| Agrupar valores por chave em listas/conjuntos | `defaultdict(list)` / `defaultdict(set)` |
| Somar valores por chave | `defaultdict(int)` / `defaultdict(float)` |
| Fila FIFO ou pilha dupla eficiente | `deque` |
| Janela deslizante (últimos N itens) | `deque(maxlen=N)` |
| Registro imutável com campos nomeados | `namedtuple` |
| Cache LRU ou reordenação de chaves | `OrderedDict` |
| Configurações em camadas sem cópia | `ChainMap` |
| Estrutura mutável com métodos e tipos | `dataclass` |

## Conclusão

O módulo `collections` é uma das partes mais subutilizadas da biblioteca padrão do Python. Ele resolve problemas que aparecem em todo código de produção: contar, agrupar, enfileirar e representar registros. Conhecer essas ferramentas transforma loops de cinco linhas em uma expressão clara, elimina bugs de inicialização e substitui reimplementações lentas por código testado e otimizado.

Para aprofundar, vale revisitar [Estruturas de Dados em Python](/blog/estruturas-de-dados-python/) para o embasamento teórico, [Geradores e Iteradores](/blog/geradores-iteradores-python/) para entender como `deque` e `Counter` se relacionam com iteráveis, e [Boas Práticas em Python 2026](/blog/boas-praticas-python-2026/) para onde encaixar essas estruturas em código idiomático moderno. O próximo passo natural depois de dominar `collections` é estudar o módulo `itertools`, que traz ferramentas equivalentes para iteração preguiçosa e combinação de iteráveis.
