Módulo collections do Python: Guia de Counter, defaultdict, deque, namedtuple
Updated on
As estruturas de dados embutidas do Python — listas, dicts, tuples, sets — dão conta da maioria das tarefas. Mas quando seu código cresce além de exemplos de brinquedo, você começa a esbarrar nos limites delas. Contar elementos exige loops manuais com dicionário. Agrupar dados significa encher o código de verificações if key not in dict. Usar uma lista como fila te pune com pop O(n) pela frente. Representar registros estruturados com tuples simples transforma o acesso aos campos em um jogo ilegível de adivinhar índices. Cada gambiarra é pequena por si só, mas elas se acumulam rápido, deixando o código mais difícil de ler, mais lento para rodar e mais propenso a quebrar.
O módulo collections na biblioteca padrão do Python resolve esses problemas com tipos de contêiner feitos sob medida. Counter conta elementos em uma chamada. defaultdict elimina KeyError com valores padrão automáticos. deque te dá operações O(1) nas duas pontas de uma sequência. namedtuple adiciona nomes de campo a tuples sem o custo de uma classe completa. OrderedDict e ChainMap lidam com ordenação e padrões de busca em camadas que dicts comuns não expressam de forma limpa.
Este guia cobre as principais classes do módulo collections com código funcional, análise de performance e padrões do mundo real. Seja você processando arquivos de log, construindo caches, gerenciando camadas de configuração ou estruturando pipelines de dados, esses contêineres vão deixar seu código mais curto, mais rápido e mais correto.
Visão geral do módulo collections
O módulo collections fornece tipos de dados de contêiner especializados que estendem os contêineres embutidos de uso geral do Python.
import collections
# See all available classes
print([name for name in dir(collections) if not name.startswith('_')])
# ['ChainMap', 'Counter', 'OrderedDict', 'UserDict', 'UserList',
# 'UserString', 'abc', 'defaultdict', 'deque', 'namedtuple']| Class | Purpose | Replaces |
|---|---|---|
Counter | Contar objetos hashable | Loops manuais de contagem com dict |
defaultdict | Dict com valores padrão automáticos | dict.setdefault(), checagens if key not in |
deque | Fila de duas pontas com extremidades O(1) | list usada como fila/pilha |
namedtuple | Tuple com campos nomeados | Tuples simples, data classes simples |
OrderedDict | Dict que lembra a ordem de inserção | dict (pré-3.7), operações ordenadas |
ChainMap | Buscas em dicionários em camadas | Merge manual de dicts |
Counter: contando elementos
Counter é uma subclasse de dict para contar objetos hashable. Ele mapeia elementos para suas contagens e fornece métodos para análise de frequência.
Criando um Counter
from collections import Counter
# From an iterable
words = ['apple', 'banana', 'apple', 'cherry', 'banana', 'apple']
word_count = Counter(words)
print(word_count)
# Counter({'apple': 3, 'banana': 2, 'cherry': 1})
# From a string
letter_count = Counter('mississippi')
print(letter_count)
# Counter({'s': 4, 'i': 4, 'p': 2, 'm': 1})
# From a dictionary
inventory = Counter({'shirts': 25, 'pants': 15, 'hats': 10})
# From keyword arguments
stock = Counter(laptops=5, monitors=12)most_common() e ranking por frequência
from collections import Counter
text = "to be or not to be that is the question"
words = Counter(text.split())
# Get the 3 most common words
print(words.most_common(3))
# [('to', 2), ('be', 2), ('or', 1)]
# Get all elements sorted by frequency
print(words.most_common())
# [('to', 2), ('be', 2), ('or', 1), ('not', 1), ('that', 1), ('is', 1), ('the', 1), ('question', 1)]
# Least common: reverse the list or slice from the end
print(words.most_common()[-3:])
# [('is', 1), ('the', 1), ('question', 1)]Aritmética com Counter
Counters suportam adição, subtração, interseção e união — tratando-os como multisets.
from collections import Counter
a = Counter(x=4, y=2, z=1)
b = Counter(x=1, y=3, z=5)
# Addition: combine counts
print(a + b) # Counter({'z': 6, 'y': 5, 'x': 5})
# Subtraction: drops zero and negative results
print(a - b) # Counter({'x': 3})
# Intersection (min of each)
print(a & b) # Counter({'y': 2, 'x': 1, 'z': 1})
# Union (max of each)
print(a | b) # Counter({'z': 5, 'x': 4, 'y': 3})Padrões práticos com Counter
from collections import Counter
# Word frequency analysis
log_entries = [
"ERROR: disk full",
"WARNING: high memory",
"ERROR: disk full",
"ERROR: timeout",
"WARNING: high memory",
"ERROR: disk full",
"INFO: backup complete",
]
error_types = Counter(entry.split(":")[0].strip() for entry in log_entries)
print(error_types)
# Counter({'ERROR': 4, 'WARNING': 2, 'INFO': 1})
# Find unique elements (count == 1)
data = [1, 2, 3, 2, 1, 4, 5, 4]
unique = [item for item, count in Counter(data).items() if count == 1]
print(unique) # [3, 5]
# Check if one collection is a subset of another (anagram check)
def is_anagram(word1, word2):
return Counter(word1.lower()) == Counter(word2.lower())
print(is_anagram("listen", "silent")) # True
print(is_anagram("hello", "world")) # FalsePara um mergulho mais profundo em Counter, veja nosso Python Counter guide.
defaultdict: valores padrão automáticos
defaultdict é uma subclasse de dict que chama uma função fábrica para fornecer valores padrão para chaves ausentes, eliminando KeyError e a necessidade de checagens defensivas.
Funções fábrica
from collections import defaultdict
# int factory: default is 0
counter = defaultdict(int)
counter['apples'] += 1
counter['oranges'] += 3
print(dict(counter)) # {'apples': 1, 'oranges': 3}
# list factory: default is []
groups = defaultdict(list)
pairs = [('fruit', 'apple'), ('veggie', 'carrot'), ('fruit', 'banana'), ('veggie', 'pea')]
for category, item in pairs:
groups[category].append(item)
print(dict(groups))
# {'fruit': ['apple', 'banana'], 'veggie': ['carrot', 'pea']}
# set factory: default is set()
index = defaultdict(set)
words = [('file1', 'python'), ('file2', 'python'), ('file1', 'java'), ('file3', 'python')]
for filename, lang in words:
index[lang].add(filename)
print(dict(index))
# {'python': {'file1', 'file2', 'file3'}, 'java': {'file1'}}O padrão de agrupamento
Agrupar dados relacionados é o uso mais comum de defaultdict(list). Compare com a abordagem manual:
from collections import defaultdict
students = [
('Math', 'Alice'), ('Science', 'Bob'), ('Math', 'Charlie'),
('Science', 'Diana'), ('Math', 'Eve'), ('History', 'Frank'),
]
# Without defaultdict -- verbose and error-prone
groups_manual = {}
for subject, name in students:
if subject not in groups_manual:
groups_manual[subject] = []
groups_manual[subject].append(name)
# With defaultdict -- clean and direct
groups = defaultdict(list)
for subject, name in students:
groups[subject].append(name)
print(dict(groups))
# {'Math': ['Alice', 'Charlie', 'Eve'], 'Science': ['Bob', 'Diana'], 'History': ['Frank']}defaultdict aninhado
Crie estruturas de dados multinível sem inicializar manualmente cada nível.
from collections import defaultdict
# Two-level nested defaultdict
def nested_dict():
return defaultdict(int)
sales = defaultdict(nested_dict)
sales['2025']['Q1'] = 150000
sales['2025']['Q2'] = 175000
sales['2026']['Q1'] = 200000
print(sales['2025']['Q1']) # 150000
print(sales['2024']['Q3']) # 0 (auto-created, no KeyError)
# Arbitrary depth nesting with a recursive factory
def deep_dict():
return defaultdict(deep_dict)
config = deep_dict()
config['database']['primary']['host'] = 'localhost'
config['database']['primary']['port'] = 5432
config['database']['replica']['host'] = 'replica.local'
print(config['database']['primary']['host']) # localhostFunções fábrica personalizadas
from collections import defaultdict
# Lambda for custom defaults
scores = defaultdict(lambda: 100) # Every student starts with 100
scores['Alice'] -= 5
scores['Bob'] -= 10
print(scores['Charlie']) # 100 (new student gets default)
print(dict(scores)) # {'Alice': 95, 'Bob': 90, 'Charlie': 100}
# Named function for complex defaults
def default_user():
return {'role': 'viewer', 'active': True, 'login_count': 0}
users = defaultdict(default_user)
users['alice']['role'] = 'admin'
print(users['bob']) # {'role': 'viewer', 'active': True, 'login_count': 0}Para mais padrões, veja nosso Python defaultdict guide.
deque: fila de duas pontas
deque (pronuncia-se "deck") fornece operações O(1) de append e pop em ambas as pontas. Listas são O(n) para pop(0) e insert(0, x) porque todos os elementos precisam ser deslocados. Para qualquer carga de trabalho que use as duas extremidades de uma sequência, deque é a escolha correta.
Operações principais
from collections import deque
d = deque([1, 2, 3, 4, 5])
# O(1) operations on both ends
d.append(6) # Add to right: [1, 2, 3, 4, 5, 6]
d.appendleft(0) # Add to left: [0, 1, 2, 3, 4, 5, 6]
right = d.pop() # Remove from right: 6
left = d.popleft() # Remove from left: 0
print(d) # deque([1, 2, 3, 4, 5])
# Extend from both sides
d.extend([6, 7]) # Right extend: [1, 2, 3, 4, 5, 6, 7]
d.extendleft([-1, 0]) # Left extend (reversed): [0, -1, 1, 2, 3, 4, 5, 6, 7]Deques limitados com maxlen
Quando maxlen é definido, adicionar elementos além do limite descarta automaticamente itens da extremidade oposta. Isso é perfeito para janelas deslizantes e caches.
from collections import deque
# Keep only the last 5 items
recent = deque(maxlen=5)
for i in range(10):
recent.append(i)
print(recent) # deque([5, 6, 7, 8, 9], maxlen=5)
# Sliding window average
def moving_average(iterable, window_size):
window = deque(maxlen=window_size)
for value in iterable:
window.append(value)
if len(window) == window_size:
yield sum(window) / window_size
data = [10, 20, 30, 40, 50, 60, 70]
print(list(moving_average(data, 3)))
# [20.0, 30.0, 40.0, 50.0, 60.0]Rotação
rotate(n) desloca os elementos n passos para a direita. Valores negativos rotacionam para a esquerda.
from collections import deque
d = deque([1, 2, 3, 4, 5])
d.rotate(2) # Rotate right by 2
print(d) # deque([4, 5, 1, 2, 3])
d.rotate(-3) # Rotate left by 3
print(d) # deque([2, 3, 4, 5, 1])Performance: deque vs list
from collections import deque
import time
# Benchmark: append/pop from left side
n = 100_000
# List: O(n) for each insert at position 0
start = time.perf_counter()
lst = []
for i in range(n):
lst.insert(0, i)
list_time = time.perf_counter() - start
# Deque: O(1) for appendleft
start = time.perf_counter()
dq = deque()
for i in range(n):
dq.appendleft(i)
deque_time = time.perf_counter() - start
print(f"List insert(0, x): {list_time:.4f}s")
print(f"Deque appendleft: {deque_time:.4f}s")
print(f"Deque is {list_time / deque_time:.0f}x faster")
# Typical output:
# List insert(0, x): 1.2340s
# Deque appendleft: 0.0065s
# Deque is 190x faster| Operation | list | deque |
|---|---|---|
append(x) (right) | O(1) amortized | O(1) |
pop() (right) | O(1) | O(1) |
insert(0, x) / appendleft(x) | O(n) | O(1) |
pop(0) / popleft() | O(n) | O(1) |
access by index [i] | O(1) | O(n) |
| Memory per element | Menor | Um pouco maior |
Use deque quando você precisa de operações rápidas nas duas pontas. Use list quando você precisa de acesso aleatório rápido por índice.
Para o guia completo, veja Python deque.
namedtuple: tuples com campos nomeados
namedtuple cria subclasses de tuple com campos nomeados, tornando o código autoexplicativo sem o overhead de definir uma classe completa.
Criando namedtuples
from collections import namedtuple
# Define a type
Point = namedtuple('Point', ['x', 'y'])
p = Point(3, 4)
# Access by name or index
print(p.x) # 3
print(p[1]) # 4
print(p) # Point(x=3, y=4)
# Alternative field definition styles
Color = namedtuple('Color', 'red green blue') # Space-separated string
Config = namedtuple('Config', 'host, port, database') # Comma-separated stringPor que usar namedtuple em vez de tuples simples?
from collections import namedtuple
# Plain tuple: which index is what?
employee_tuple = ('Alice', 'Engineering', 95000, True)
print(employee_tuple[2]) # 95000 -- but what does index 2 mean?
# namedtuple: self-documenting
Employee = namedtuple('Employee', 'name department salary active')
employee = Employee('Alice', 'Engineering', 95000, True)
print(employee.salary) # 95000 -- immediately clear
print(employee.department) # EngineeringMétodos principais
from collections import namedtuple
Employee = namedtuple('Employee', 'name department salary')
emp = Employee('Alice', 'Engineering', 95000)
# _replace: create a new instance with some fields changed (immutable)
promoted = emp._replace(salary=110000)
print(promoted) # Employee(name='Alice', department='Engineering', salary=110000)
print(emp) # Employee(name='Alice', department='Engineering', salary=95000) -- unchanged
# _asdict: convert to OrderedDict (Python 3.8+ returns regular dict)
print(emp._asdict())
# {'name': 'Alice', 'department': 'Engineering', 'salary': 95000}
# _fields: get field names
print(Employee._fields) # ('name', 'department', 'salary')
# _make: create from an iterable
data = ['Bob', 'Marketing', 85000]
emp2 = Employee._make(data)
print(emp2) # Employee(name='Bob', department='Marketing', salary=85000)Valores padrão
from collections import namedtuple
# defaults parameter (Python 3.6.1+)
Connection = namedtuple('Connection', 'host port timeout', defaults=[5432, 30])
conn1 = Connection('localhost') # port=5432, timeout=30
conn2 = Connection('db.example.com', 3306) # timeout=30
conn3 = Connection('db.example.com', 3306, 60)
print(conn1) # Connection(host='localhost', port=5432, timeout=30)
print(conn2) # Connection(host='db.example.com', port=3306, timeout=30)Alternativa: typing.NamedTuple
Para anotações de tipo e uma sintaxe mais parecida com classe, use typing.NamedTuple:
from typing import NamedTuple
class Point(NamedTuple):
x: float
y: float
label: str = "origin"
p = Point(3.0, 4.0, "A")
print(p.x, p.label) # 3.0 A
# Still a tuple -- supports unpacking, indexing, iteration
x, y, label = p
print(f"({x}, {y})") # (3.0, 4.0)namedtuple vs dataclass
| Feature | namedtuple | dataclass |
|---|---|---|
| Imutável por padrão | Sim | Não (frozen=True é necessário) |
| Pegada de memória | Igual à tuple (pequena) | Maior (classe regular) |
| Iteração/unpacking | Sim (é uma tuple) | Não (a menos que você adicione métodos) |
| Anotações de tipo | Via typing.NamedTuple | Nativo |
| Métodos/propriedades | Exige subclassing | Suporte direto |
| Herança | Limitada | Herança completa de classes |
| Melhor para | Registros leves de dados | Objetos complexos mutáveis |
OrderedDict: operações de dicionário ordenadas
Desde o Python 3.7, o dict regular preserva a ordem de inserção. Então, quando você ainda precisa de OrderedDict?
Quando OrderedDict ainda faz diferença
from collections import OrderedDict
# 1. Equality considers order
d1 = {'a': 1, 'b': 2}
d2 = {'b': 2, 'a': 1}
print(d1 == d2) # True -- regular dicts ignore order in comparison
od1 = OrderedDict([('a', 1), ('b', 2)])
od2 = OrderedDict([('b', 2), ('a', 1)])
print(od1 == od2) # False -- OrderedDict considers order
# 2. move_to_end() for reordering
od = OrderedDict([('a', 1), ('b', 2), ('c', 3)])
od.move_to_end('a') # Move 'a' to the end
print(list(od.keys())) # ['b', 'c', 'a']
od.move_to_end('c', last=False) # Move 'c' to the beginning
print(list(od.keys())) # ['c', 'b', 'a']Construindo um cache LRU com OrderedDict
from collections import OrderedDict
class LRUCache:
def __init__(self, capacity):
self.cache = OrderedDict()
self.capacity = capacity
def get(self, key):
if key not in self.cache:
return -1
self.cache.move_to_end(key) # Mark as recently used
return self.cache[key]
def put(self, key, value):
if key in self.cache:
self.cache.move_to_end(key)
self.cache[key] = value
if len(self.cache) > self.capacity:
self.cache.popitem(last=False) # Remove oldest
cache = LRUCache(3)
cache.put('a', 1)
cache.put('b', 2)
cache.put('c', 3)
cache.get('a') # Access 'a', moves it to end
cache.put('d', 4) # Evicts 'b' (least recently used)
print(list(cache.cache.keys())) # ['c', 'a', 'd']ChainMap: buscas em dicionários em camadas
ChainMap agrupa múltiplos dicionários em uma única “visão” para buscas. Ele procura em cada dicionário em ordem, retornando a primeira correspondência. Isso é ideal para camadas de configuração, busca de variáveis por escopo e gerenciamento de contexto.
Uso básico
from collections import ChainMap
defaults = {'theme': 'light', 'language': 'en', 'timeout': 30}
user_prefs = {'theme': 'dark'}
session = {'language': 'fr'}
config = ChainMap(session, user_prefs, defaults)
# Lookup searches session -> user_prefs -> defaults
print(config['theme']) # 'dark' (from user_prefs)
print(config['language']) # 'fr' (from session)
print(config['timeout']) # 30 (from defaults)Camadas de configuração
from collections import ChainMap
import os
# Real-world config pattern: CLI args > env vars > config file > defaults
defaults = {
'debug': False,
'log_level': 'WARNING',
'port': 8080,
'host': '0.0.0.0',
}
config_file = {
'log_level': 'INFO',
'port': 9090,
}
env_vars = {
k.lower(): v for k, v in os.environ.items()
if k.lower() in defaults
}
cli_args = {'debug': True} # Parsed from argparse
config = ChainMap(cli_args, env_vars, config_file, defaults)
print(config['debug']) # True (from cli_args)
print(config['log_level']) # 'INFO' (from config_file)
print(config['host']) # '0.0.0.0' (from defaults)Contextos com escopo via new_child()
from collections import ChainMap
# Simulating variable scoping (like nested function scopes)
global_scope = {'x': 1, 'y': 2}
local_scope = ChainMap(global_scope)
# Enter a new scope
inner_scope = local_scope.new_child()
inner_scope['x'] = 10 # Shadows global x
inner_scope['z'] = 30 # New local variable
print(inner_scope['x']) # 10 (local)
print(inner_scope['y']) # 2 (falls through to global)
print(inner_scope['z']) # 30 (local)
# Exit scope -- original is unchanged
print(local_scope['x']) # 1 (global still intact)Comparação de todos os tipos do collections
| Type | Base Class | Mutable | Use Case | Key Advantage |
|---|---|---|---|---|
Counter | dict | Sim | Contar elementos | most_common(), aritmética de multiset |
defaultdict | dict | Sim | Auto-inicializar chaves ausentes | Sem KeyError, funções fábrica |
deque | -- | Sim | Fila de duas pontas | O(1) nas duas pontas, maxlen |
namedtuple | tuple | Não | Registros estruturados | Acesso por campos nomeados, leve |
OrderedDict | dict | Sim | Dicts sensíveis à ordem | move_to_end(), igualdade por ordem |
ChainMap | -- | Sim | Buscas em camadas | Camadas de config, contextos com escopo |
Benchmarks de performance
Counter vs contagem manual
from collections import Counter, defaultdict
import time
data = list(range(1000)) * 1000 # 1 million items, 1000 unique
# Method 1: Counter
start = time.perf_counter()
c = Counter(data)
counter_time = time.perf_counter() - start
# Method 2: defaultdict(int)
start = time.perf_counter()
dd = defaultdict(int)
for item in data:
dd[item] += 1
dd_time = time.perf_counter() - start
# Method 3: Manual dict
start = time.perf_counter()
manual = {}
for item in data:
manual[item] = manual.get(item, 0) + 1
manual_time = time.perf_counter() - start
print(f"Counter: {counter_time:.4f}s")
print(f"defaultdict(int):{dd_time:.4f}s")
print(f"dict.get(): {manual_time:.4f}s")
# Typical: Counter ~0.03s, defaultdict ~0.07s, dict.get() ~0.09sdeque vs list para operações de fila
from collections import deque
import time
n = 100_000
# Simulate a FIFO queue: append right, pop left
# List
start = time.perf_counter()
q = list(range(n))
while q:
q.pop(0)
list_queue_time = time.perf_counter() - start
# Deque
start = time.perf_counter()
q = deque(range(n))
while q:
q.popleft()
deque_queue_time = time.perf_counter() - start
print(f"List pop(0): {list_queue_time:.4f}s")
print(f"Deque popleft(): {deque_queue_time:.4f}s")
print(f"Deque is {list_queue_time / deque_queue_time:.0f}x faster")
# Typical: List ~2.5s, Deque ~0.004s -> ~600x fasterExemplos do mundo real
Análise de logs com Counter
from collections import Counter
from datetime import datetime
# Parse and analyze server logs
log_lines = [
"2026-02-18 10:15:03 GET /api/users 200",
"2026-02-18 10:15:04 POST /api/login 401",
"2026-02-18 10:15:05 GET /api/users 200",
"2026-02-18 10:15:06 GET /api/products 500",
"2026-02-18 10:15:07 POST /api/login 200",
"2026-02-18 10:15:08 GET /api/users 200",
"2026-02-18 10:15:09 GET /api/products 500",
"2026-02-18 10:15:10 POST /api/login 401",
]
# Count status codes
status_codes = Counter(line.split()[-1] for line in log_lines)
print("Status codes:", status_codes.most_common())
# [('200', 4), ('401', 2), ('500', 2)]
# Count endpoints
endpoints = Counter(line.split()[3] for line in log_lines)
print("Top endpoints:", endpoints.most_common(2))
# [('/api/users', 3), ('/api/login', 3)]
# Count error endpoints (status >= 400)
errors = Counter(
line.split()[3] for line in log_lines
if int(line.split()[-1]) >= 400
)
print("Error endpoints:", errors)
# Counter({'/api/login': 2, '/api/products': 2})Gerenciamento de configuração com ChainMap
from collections import ChainMap
import json
# Multi-layer config system for a web application
def load_config(config_path=None, cli_overrides=None):
# Layer 1: Hard-coded defaults
defaults = {
'host': '127.0.0.1',
'port': 8000,
'debug': False,
'db_pool_size': 5,
'log_level': 'WARNING',
'cors_origins': ['http://localhost:3000'],
}
# Layer 2: Config file
file_config = {}
if config_path:
with open(config_path) as f:
file_config = json.load(f)
# Layer 3: CLI overrides (highest priority)
cli = cli_overrides or {}
# ChainMap searches cli -> file_config -> defaults
return ChainMap(cli, file_config, defaults)
# Usage
config = load_config(cli_overrides={'debug': True, 'port': 9000})
print(config['debug']) # True (CLI override)
print(config['port']) # 9000 (CLI override)
print(config['db_pool_size']) # 5 (default)
print(config['log_level']) # WARNING (default)Cache de itens recentes com deque
from collections import deque
class RecentItemsTracker:
"""Track the N most recent unique items."""
def __init__(self, max_items=10):
self.items = deque(maxlen=max_items)
self.seen = set()
def add(self, item):
if item in self.seen:
# Move to front by removing and re-adding
self.items.remove(item)
self.items.append(item)
else:
if len(self.items) == self.items.maxlen:
# Remove the oldest item from the set too
oldest = self.items[0]
self.seen.discard(oldest)
self.items.append(item)
self.seen.add(item)
def get_recent(self):
return list(reversed(self.items))
# Track recently viewed products
tracker = RecentItemsTracker(max_items=5)
for product in ['shoes', 'shirt', 'hat', 'shoes', 'jacket', 'belt', 'hat']:
tracker.add(product)
print(tracker.get_recent())
# ['hat', 'belt', 'jacket', 'shoes', 'shirt']Pipeline de dados com namedtuple
from collections import namedtuple, Counter, defaultdict
# Define structured records
Transaction = namedtuple('Transaction', 'id customer product amount date')
transactions = [
Transaction(1, 'Alice', 'Widget', 29.99, '2026-02-01'),
Transaction(2, 'Bob', 'Gadget', 49.99, '2026-02-01'),
Transaction(3, 'Alice', 'Widget', 29.99, '2026-02-03'),
Transaction(4, 'Charlie', 'Gadget', 49.99, '2026-02-05'),
Transaction(5, 'Alice', 'Gizmo', 19.99, '2026-02-07'),
Transaction(6, 'Bob', 'Widget', 29.99, '2026-02-08'),
]
# Most popular products
product_count = Counter(t.product for t in transactions)
print("Popular products:", product_count.most_common())
# [('Widget', 3), ('Gadget', 2), ('Gizmo', 1)]
# Revenue by customer
revenue = defaultdict(float)
for t in transactions:
revenue[t.customer] += t.amount
print("Revenue:", dict(revenue))
# {'Alice': 79.97, 'Bob': 79.98, 'Charlie': 49.99}
# Convert to DataFrame for visualization
import pandas as pd
df = pd.DataFrame(transactions, columns=Transaction._fields)
print(df.groupby('customer')['amount'].sum())Visualizando dados de collections com PyGWalker
Depois de processar dados com Counter, defaultdict ou namedtuple, você frequentemente quer visualizar os resultados. PyGWalker (opens in a new tab) transforma qualquer pandas DataFrame em uma interface de visualização interativa no estilo Tableau diretamente em notebooks Jupyter:
from collections import Counter
import pandas as pd
import pygwalker as pyg
# Process data with collections
log_data = ["ERROR", "WARNING", "ERROR", "INFO", "ERROR", "WARNING", "INFO", "INFO"]
counts = Counter(log_data)
# Convert to DataFrame
df = pd.DataFrame(counts.items(), columns=['Level', 'Count'])
# Launch interactive visualization
walker = pyg.walk(df)Isso permite arrastar e soltar campos, criar gráficos, filtrar dados e explorar padrões de forma interativa — sem escrever código de visualização. É especialmente útil quando você tem datasets grandes processados via agrupamentos com Counter ou defaultdict e quer explorar as distribuições visualmente.
Para executar esses experimentos com collections de forma interativa, RunCell (opens in a new tab) oferece um ambiente Jupyter com IA onde você pode iterar em pipelines de processamento de dados com feedback instantâneo.
Combinando múltiplos tipos de collections
O verdadeiro poder de collections aparece quando você combina tipos em um único pipeline.
from collections import Counter, defaultdict, namedtuple, deque
# Named record type
LogEntry = namedtuple('LogEntry', 'timestamp level message')
# Simulated log stream
log_stream = deque([
LogEntry('10:01', 'ERROR', 'Connection timeout'),
LogEntry('10:02', 'INFO', 'Request processed'),
LogEntry('10:03', 'ERROR', 'Connection timeout'),
LogEntry('10:04', 'WARNING', 'High memory'),
LogEntry('10:05', 'ERROR', 'Disk full'),
LogEntry('10:06', 'INFO', 'Request processed'),
LogEntry('10:07', 'ERROR', 'Connection timeout'),
], maxlen=100)
# Count error types
error_counts = Counter(
entry.message for entry in log_stream if entry.level == 'ERROR'
)
print("Error types:", error_counts.most_common())
# [('Connection timeout', 3), ('Disk full', 1)]
# Group entries by level
by_level = defaultdict(list)
for entry in log_stream:
by_level[entry.level].append(entry)
for level, entries in by_level.items():
print(f"{level}: {len(entries)} entries")
# ERROR: 4 entries
# INFO: 2 entries
# WARNING: 1 entriesFAQ
O que é o módulo collections do Python?
O módulo collections faz parte da biblioteca padrão do Python. Ele fornece tipos de dados de contêiner especializados que estendem os tipos embutidos (dict, list, tuple, set) com funcionalidades adicionais. As principais classes são Counter, defaultdict, deque, namedtuple, OrderedDict e ChainMap. Cada uma resolve uma categoria específica de problemas de manipulação de dados com mais eficiência do que usar apenas os tipos embutidos.
Quando devo usar Counter vs defaultdict(int)?
Use Counter quando seu objetivo principal for contar elementos ou comparar distribuições de frequência. Ele fornece most_common(), operadores aritméticos (+, -, &, |) e consegue contar um iterável inteiro em uma única chamada do construtor. Use defaultdict(int) quando a contagem for algo secundário dentro de um padrão mais amplo de estrutura de dados, ou quando você precisar de um dicionário de uso geral com padrões inteiros.
deque é thread-safe no Python?
Sim. No CPython, deque.append(), deque.appendleft(), deque.pop() e deque.popleft() são operações atômicas por causa do GIL (Global Interpreter Lock). Isso torna deque seguro para uso como uma fila thread-safe sem locking adicional. No entanto, operações compostas (como sequências “checar-então-agir”) ainda precisam de sincronização explícita.
Qual é a diferença entre namedtuple e dataclass?
namedtuple cria subclasses imutáveis de tuple com campos nomeados. É leve, suporta iteração e unpacking e usa pouca memória. dataclass (do módulo dataclasses, Python 3.7+) cria classes completas com atributos mutáveis por padrão, suportando métodos, propriedades e herança. Use namedtuple para registros de dados simples e imutáveis. Use dataclass quando você precisar de mutabilidade, comportamento complexo ou anotações de tipo extensas.
OrderedDict ainda importa no Python 3.7+?
Sim, em dois casos específicos. Primeiro, comparações de igualdade de OrderedDict consideram a ordem dos elementos (OrderedDict(a=1, b=2) != OrderedDict(b=2, a=1)), enquanto dicts regulares não. Segundo, OrderedDict fornece move_to_end() para reordenar elementos, o que é útil para implementar caches LRU e estruturas baseadas em prioridade. Para todos os outros casos de uso, dict regular é suficiente e mais performático.
Como ChainMap difere de mesclar dicionários?
ChainMap cria uma “visão” sobre múltiplos dicionários sem copiar dados. As buscas percorrem cada dicionário em ordem. Mudanças nos dicionários subjacentes são refletidas imediatamente no ChainMap. Em contraste, mesclar com {**d1, **d2} ou d1 | d2 cria um novo dicionário, duplicando todos os dados. ChainMap é mais eficiente em memória para dicionários grandes e preserva a estrutura em camadas para padrões de configuração e escopo.
Posso usar tipos de collections com type hints?
Sim. Use collections.Counter[str] para counters tipados, collections.defaultdict[str, list[int]] para defaultdicts tipados e collections.deque[int] para deques tipados. Para namedtuple, prefira typing.NamedTuple, que suporta anotações de tipo diretamente na definição da classe. Todos os tipos de collections são totalmente compatíveis com mypy e outros type checkers.
Conclusão
O módulo collections do Python fornece seis tipos de contêiner especializados que eliminam padrões comuns de boilerplate. Counter substitui loops manuais de contagem. defaultdict remove a necessidade de tratar KeyError. deque oferece operações rápidas de duas pontas. namedtuple adiciona nomes de campo legíveis às tuples. OrderedDict lida com comparações sensíveis à ordem e reordenação. ChainMap gerencia buscas em camadas de dicionários sem duplicar dados.
Cada tipo resolve um problema específico melhor do que os contêineres embutidos. Aprender quando usar cada um vai deixar seu código Python mais curto, mais rápido e mais fácil de manter. A chave é combinar a estrutura de dados ao padrão de operações: contagem (Counter), agrupamento (defaultdict), fila/pilha (deque), registros estruturados (namedtuple), operações ordenadas (OrderedDict) e buscas em camadas (ChainMap).