collections в Python: Counter, defaultdict и deque
Стандартный модуль collections предлагает контейнеры для распространённых моделей данных. Counter считает хешируемые элементы, defaultdict создаёт значение при обращении к…
Стандартный модуль collections предлагает контейнеры для распространённых моделей данных. Counter считает хешируемые элементы, defaultdict создаёт значение при обращении к отсутствующему ключу, а deque быстро добавляет и удаляет элементы с обоих концов. Выбор подходящего контейнера делает намерение кода заметным и убирает ручные проверки.
Как выбрать контейнер
Counter похож на словарь «элемент → количество» и поддерживает most_common, обновление и арифметику счётчиков. Для неизвестного ключа чтение возвращает ноль, не добавляя обычную проверку. defaultdict(factory) вызывает фабрику без аргументов и сохраняет созданное значение при доступе через квадратные скобки.
deque представляет двустороннюю очередь. Операции append, appendleft, pop и popleft работают на соответствующих концах без сдвига всех оставшихся элементов. Параметр maxlen полезен для скользящего окна: при переполнении старые элементы автоматически вытесняются с противоположной стороны.
Рабочий пример: обработка очереди задач
from collections import Counter, defaultdict, deque
queue = deque([
('api', 'ok'),
('worker', 'error'),
('api', 'error'),
])
statuses = Counter()
errors_by_service = defaultdict(list)
while queue:
service, status = queue.popleft()
statuses[status] += 1
if status == 'error':
errors_by_service[service].append(status)
print(statuses['error'])
print(dict(errors_by_service))Ожидаемый результат:
2
{'worker': ['error'], 'api': ['error']}Очередь явно обрабатывается слева направо. Счётчик отвечает за агрегат по статусу, а словарь со списками — за группировку ошибок по сервисам. Преобразование dict(...) в последней строке нужно лишь для компактного предсказуемого отображения примера.
Фабрика defaultdict и пустая deque
Для defaultdict(list) типичная ошибка — передать результат вызова: defaultdict(list()). Конструктор сообщает TypeError: first argument must be callable or None, поскольку ему нужна фабрика, которую можно запускать для каждого нового ключа. Проверьте callable(default_factory) и передавайте list, set или собственную функцию без скобок.
При IndexError: pop from an empty deque проверьте условие потребления и число удалений. Безопасная очередь обычно читается под while queue, а конкурентный обмен требует отдельной синхронизации — сам выбор deque не превращает многошаговую проверку и удаление в единую операцию. Если счётчики неожиданно отрицательны, найдите места, где применяются subtract или ручное уменьшение: Counter допускает нулевые и отрицательные количества.
Тренировка: частоты, группы и окно
По строке 'abracadabra' постройте Counter, выведите три наиболее частые буквы и отдельно количество 'z'. Затем сгруппируйте слова ['кот', 'кит', 'дом', 'крот'] в defaultdict(list) по первой букве. Для очереди последних трёх измерений создайте deque(maxlen=3) и последовательно добавьте 10, 20, 30, 40; ожидаемое содержимое — [20, 30, 40].
Самостоятельная проверка: замените фабрику списка на set и измените добавление на add. Намеренно напишите defaultdict(set()), прочитайте тип исключения и объясните, почему контейнеру нужен вызываемый объект, а не одна общая заранее созданная коллекция.
Контейнер выбирают по операции
Counter выражает частоты, defaultdict централизует создание значений отсутствующих ключей, deque обслуживает оба конца очереди. Эти классы не просто сокращают запись: они фиксируют модель данных. Фабрика defaultdict передаётся без вызова, а удаление из пустой очереди всё равно требует продуманного условия.
Counter, defaultdict и deque на практике
Добавляет ли Counter отсутствующий ключ при чтении?
Выражение counter[key] возвращает ноль, но простое чтение не обязано сохранять ключ как обычную запись. Наличие проверяйте оператором in, если оно имеет отдельный смысл.
Чем defaultdict отличается от setdefault?
defaultdict задаёт одно правило для всех пропущенных ключей. dict.setdefault применяется в конкретном месте и вычисляет переданный аргумент до вызова, даже если ключ уже существует.
Почему для очереди не взять обычный список?
Добавление и удаление в конце списка удобны. Удаление первого элемента требует сдвигов, поэтому для регулярного обслуживания обоих концов семантика и характеристики deque подходят лучше.