collections в Python: defaultdict, Counter и deque — практическое руководство с примерами
Модуль collections в Python — это готовые структуры данных с оптимизированным поведением. В этой статье мы разберём три самых полезных инструмента: defaultdict, Counter и deque. Если вы ищете запрос вроде «collections в Python: defaultdict, Counter, deque», вы по адресу — ниже только практика и понятные примеры.
Когда использовать collections вместо list и dict
defaultdict: словарь со значением по умолчанию
defaultdict автоматически создаёт значение для отсутствующего ключа. Это упрощает группировку и накопление данных.
from collections import defaultdict
names = ["Анна", "Андрей", "Борис", "Белла", "Гена"]
groups = defaultdict(list)
for name in names:
groups[name[0]].append(name)
print(dict(groups)) # {'А': ['Анна', 'Андрей'], 'Б': ['Борис', 'Белла'], 'Г': ['Гена']}
Типичный кейс — подсчёты без лишних проверок:
from collections import defaultdict
counts = defaultdict(int)
for word in "мир мир труд май труд".split():
counts[word] += 1
print(dict(counts)) # {'мир': 2, 'труд': 2, 'май': 1}
Полезные советы по defaultdict
default_factory из готовых типов: list, set, int, float — это безопасно и быстро.d[key] создаст ключ, что может «раздувать» словарь при чтении.dict:import json
from collections import defaultdict
data = defaultdict(list, users=["ann"])
# json.dumps(data) # TypeError
print(json.dumps(dict(data))) # OK
Частые ошибки с defaultdict
# Ошибка: одна и та же ссылка на список для всех ключей!
bad_list = []
d = defaultdict(lambda: bad_list)
d['x'].append(1)
d['y'].append(2)
print(d) # оба ключа указывают на один и тот же список
# Правильно:
d = defaultdict(list)
Counter: частоты и топы за две строки
Counter — специальный словарь для подсчёта частот: слов, букв, статусов HTTP, событий и т.п.
from collections import Counter
text = "мир мир труд май труд"
c = Counter(text.split())
print(c) # Counter({'мир': 2, 'труд': 2, 'май': 1})
print(c.most_common(2)) # [('мир', 2), ('труд', 2)]
Обновление и операции над счётчиками:
c1 = Counter("aabbb")
c2 = Counter("bbcc")
print(c1 + c2) # Counter({'b': 5, 'a': 2, 'c': 2}) — поэлементная сумма
print(c1 - c2) # Counter({'a': 2, 'b': 1}) — только положительные разности
c = Counter()
c.update(["ok", "ok", "err"]) # {'ok': 2, 'err': 1}
c.update(["ok"]) # {'ok': 3, 'err': 1}
print(c.elements()) # итератор повторений по частоте
Подводные камни Counter
subtract может сделать счётчики отрицательными. Очистить нули/минусы можно так: c += Counter() или c = +c.most_common возвращает результаты в порядке убывания, но элементы с одинаковой частотой могут идти в любом порядке — не полагайтесь на стабильность для тай-брейков.from collections import Counter
c = Counter(a=2, b=1)
c.subtract({'a': 3}) # a: -1
print(c) # Counter({'b': 1, 'a': -1})
print(+c) # Counter({'b': 1}) — убраны нули и отрицательные
deque: двусторонняя очередь для быстрых операций
deque обеспечивает амортизированную O(1) вставку и удаление с обоих концов. Идеально для очередей, слайдинговых окон и LRU-паттернов.
from collections import deque
dq = deque(maxlen=3)
for x in [1, 2, 3, 4]:
dq.append(x)
print(dq) # deque([2, 3, 4], maxlen=3)
dq.appendleft(0)
print(dq) # deque([0, 2, 3], maxlen=3)
dq.rotate(1)
print(dq) # deque([3, 0, 2], maxlen=3)
Скользящее среднее за окно фиксированного размера:
from collections import deque
def moving_avg(nums, k):
q, s = deque(), 0
for x in nums:
q.append(x); s += x
if len(q) > k:
s -= q.popleft()
if len(q) == k:
yield s / k
print(list(moving_avg([1, 2, 3, 4, 5], 3))) # [2.0, 3.0, 4.0]
Комбинированный пример: мини‑аналитика логов
Посчитаем частоты статусов, определим самый частый статус по каждому пути и сохраним последние N ошибок.
from collections import Counter, defaultdict, deque
logs = [
"200 /index", "404 /favicon.ico", "200 /api", "500 /api", "200 /index",
]
# 1) Частоты статусов
statuses = Counter(int(line.split()[0]) for line in logs)
print(statuses.most_common())
# 2) Самый частый статус по каждому пути
by_path = defaultdict(Counter)
for line in logs:
status, path = line.split()
by_path[path].update([int(status)])
print({p: c.most_common(1)[0] for p, c in by_path.items()})
# 3) Последние 2 ошибки
last_errors = deque(maxlen=2)
for line in logs:
if line.startswith(("4", "5")):
last_errors.append(line)
print(list(last_errors))
Лучшие практики и рекомендации
defaultdict наружу — приводите к dict, чтобы не удивлять клиентов функции.Counter и defaultdict(int) работают заметно быстрее, чем «ручная» логика с проверками ключей.most_common с одинаковыми частотами не проверяйте порядок — сравнивайте множества или сортируйте вручную по второму критерию.Частые вопросы и ошибки
defaultdict создаёт ключ при чтении? Так устроен __missing__: обращение d[key] создаёт значение. Для «безопасной» проверки используйте key in d или d.get(key).Counter.subtract делает отрицательные счётчики? Это ожидаемо: используйте +c или c += Counter() для удаления нулей и отрицательных значений.defaultdict(list) вместо «если ключа нет — создай список»? Всегда, когда вы группируете элементы — код короче и быстрее.Итоги
defaultdict, Counter и deque — фундаментальные инструменты модуля collections в Python. Они делают код короче, надёжнее и быстрее, если применять их по назначению. Освойте их — и многие повседневные задачи (подсчёты, группировки, очереди) будут решаться в пару строк.
Хотите системно прокачать Python и закрепить всё на проектах? Посмотрите программу и начните сегодня: Прокачать Python на практике — записаться на курс «Python с Нуля до Гуру» →




