Python: collections модуль – специальные контейнеры для эффективного кода
Для кого эта статья:
- Разработчики Python, желающие улучшить свои навыки программирования
- Студенты и учащиеся курсов по программированию на Python
Специалисты, занимающиеся оптимизацией кода и производительностью программ
Модуль collections в Python — настоящая золотая жила для разработчиков, которые хотят писать элегантный, эффективный и производительный код. Если вы до сих пор используете только базовые структуры данных вроде списков и словарей, вы упускаете огромный потенциал для оптимизации ваших программ. Специализированные контейнеры из collections помогают не только сократить количество строк кода, но и значительно улучшить его читаемость и производительность — это как перейти с обычной отвертки на электрическую в мире программирования. 🛠️
Изучение продвинутых модулей вроде collections — важный шаг к профессиональному уровню программирования на Python. На курсе Обучение Python-разработке от Skypro вы не только освоите этот и другие полезные модули, но и научитесь применять их в реальных проектах под руководством практикующих разработчиков. Вместо самостоятельного изучения получите структурированные знания и обратную связь от экспертов, что значительно ускорит ваш путь к позиции Python-разработчика.
Основные возможности модуля collections в Python
Модуль collections входит в стандартную библиотеку Python и предоставляет специализированные контейнерные типы данных, которые расширяют функциональность встроенных контейнеров, таких как list, dict и tuple. Эти альтернативные структуры данных оптимизированы для конкретных сценариев использования, что делает их незаменимыми инструментами в арсенале разработчика. 🧰
Основные классы модуля collections включают:
- Counter — подкласс dict для подсчета хешируемых объектов
- defaultdict — словарь с фабричной функцией для отсутствующих ключей
- OrderedDict — словарь, запоминающий порядок добавления ключей
- namedtuple — фабрика для создания классов кортежей с именованными полями
- deque — двусторонняя очередь с эффективными операциями вставки и извлечения с обоих концов
- ChainMap — объединение нескольких словарей в единое представление
Давайте рассмотрим, когда и как использовать каждый из этих типов данных.
| Класс | Основное применение | Когда использовать |
|---|---|---|
| Counter | Подсчёт элементов | Статистика, частотный анализ |
| defaultdict | Автоматическая инициализация | Группировка, многоуровневые словари |
| OrderedDict | Сохранение порядка ключей | Сохранение последовательности (до Python 3.7) |
| namedtuple | Легковесные объекты | Структурированные данные, когда не нужен полноценный класс |
| deque | Двусторонние операции | Очереди FIFO/LIFO, скользящие окна |
| ChainMap | Объединение словарей | Контексты поиска, множественные пространства имен |
Импортировать модуль collections можно несколькими способами:
# Импортировать весь модуль
import collections
# Импортировать только нужные классы
from collections import Counter, defaultdict, namedtuple

Counter: подсчет элементов в коллекциях Python
Counter — это подкласс словаря, специально созданный для подсчета хешируемых объектов. Он возвращает словарь, где ключи — это элементы, а значения — их количество. Представьте, что вы пытаетесь подсчитать частоту слов в тексте или карты в колоде. Counter делает эту задачу тривиальной. 📊
Александр Петров, ведущий Python-разработчик
Когда я анализировал большой корпус текстов для проекта по обработке естественного языка, мне нужно было определить частотность слов. Первоначально я использовал обычный словарь:
PythonСкопировать кодword_freq = {} for word in text.split(): if word in word_freq: word_freq[word] += 1 else: word_freq[word] = 1Этот код работал, но выглядел громоздко. Когда я заменил его на Counter, код стал не только короче, но и в 1.5 раза быстрее:
PythonСкопировать кодfrom collections import Counter word_freq = Counter(text.split())Более того, Counter предоставил мне удобные методы для анализа, например, most_common() для нахождения наиболее частых слов. Это сэкономило мне часы работы и сделало код намного читабельнее.
Основные способы инициализации Counter:
from collections import Counter
# Из итерируемого объекта
c = Counter(['a', 'b', 'c', 'a', 'b', 'b']) # Counter({'b': 3, 'a': 2, 'c': 1})
# Из строки
c = Counter('abracadabra') # Counter({'a': 5, 'b': 2, 'r': 2, 'c': 1, 'd': 1})
# Из ключевых аргументов
c = Counter(a=3, b=1, c=5) # Counter({'c': 5, 'a': 3, 'b': 1})
# Из словаря
c = Counter({'a': 4, 'b': 2}) # Counter({'a': 4, 'b': 2})
Counter поддерживает несколько полезных методов и операций:
- most_common([n]) — возвращает список из n наиболее часто встречающихся элементов и их количества
- elements() — возвращает итератор по элементам, повторяя каждый столько раз, сколько указано в счетчике
- subtract([iterable-or-mapping]) — вычитает элементы из итерируемого объекта или маппинга
- update([iterable-or-mapping]) — аналогично, но увеличивает счетчики
Counter также поддерживает арифметические операции:
c = Counter(a=3, b=1)
d = Counter(a=1, b=2)
print(c + d) # Counter({'a': 4, 'b': 3})
print(c – d) # Counter({'a': 2})
Практические применения Counter:
- Анализ текста и подсчет частоты слов или символов
- Нахождение наиболее/наименее частых элементов в последовательности
- Сравнение частотных характеристик разных последовательностей
- Реализация алгоритмов, требующих подсчета (например, алгоритм Бойера-Мура для поиска подстрок)
defaultdict и OrderedDict: улучшенные словари на все случаи
Стандартные словари Python чрезвычайно полезны, но у них есть ограничения. Модуль collections предлагает две мощные альтернативы: defaultdict и OrderedDict, которые решают специфические проблемы и делают код более элегантным. 🗂️
defaultdict: прощай, KeyError!
defaultdict — это подкласс dict, который автоматически создает значение для отсутствующего ключа с помощью фабричной функции, переданной в конструктор. Это избавляет от необходимости проверять существование ключа перед обращением к нему.
from collections import defaultdict
# Создаем словарь с дефолтным значением 0 для любого нового ключа
word_count = defaultdict(int)
for word in ['apple', 'orange', 'apple', 'banana', 'orange', 'orange']:
word_count[word] += 1 # Никаких KeyError!
print(word_count)
# defaultdict(<class 'int'>, {'apple': 2, 'orange': 3, 'banana': 1})
Фабричная функция может быть любым вызываемым объектом, возвращающим значение:
# Словарь списков
grouped = defaultdict(list)
for key, value in [('a', 1), ('b', 2), ('a', 3), ('b', 4)]:
grouped[key].append(value)
# {'a': [1, 3], 'b': [2, 4]}
# Словарь словарей
nested = defaultdict(dict)
nested['user1']['name'] = 'Alice' # Работает без инициализации nested['user1']
# Собственная фабричная функция
def default_factory():
return "Not found"
d = defaultdict(default_factory)
print(d['key']) # 'Not found'
Особенно полезен defaultdict при группировке или подсчете:
# Группировка элементов по категориям
categorized = defaultdict(list)
for item in items:
categorized[item.category].append(item)
# Подсчет количества элементов
counter = defaultdict(int)
for item in items:
counter[item] += 1
OrderedDict: порядок имеет значение
OrderedDict — это подкласс dict, который запоминает порядок вставки ключей. Начиная с Python 3.7, обычные словари также сохраняют порядок вставки, но OrderedDict имеет дополнительные методы и сохраняет порядок при некоторых операциях, где обычный dict его теряет.
from collections import OrderedDict
# Создаем упорядоченный словарь
d = OrderedDict([('apple', 4), ('banana', 3), ('orange', 2), ('pear', 1)])
# Перемещаем ключ в конец
d.move_to_end('banana')
print(d) # OrderedDict([('apple', 4), ('orange', 2), ('pear', 1), ('banana', 3)])
# Перемещаем ключ в начало
d.move_to_end('pear', last=False)
print(d) # OrderedDict([('pear', 1), ('apple', 4), ('orange', 2), ('banana', 3)])
# Удаляем и возвращаем пару (ключ, значение) с последнего конца
key, value = d.popitem()
print(key, value) # 'banana', 3
# Удаляем с первого конца
key, value = d.popitem(last=False)
print(key, value) # 'pear', 1
Сравнение defaultdict и OrderedDict:
| Характеристика | defaultdict | OrderedDict | Стандартный dict (≥ Python 3.7) |
|---|---|---|---|
| Автоматическое создание значений | Да | Нет | Нет |
| Сохранение порядка вставки | Да (≥ Python 3.7) | Да | Да |
| Специальные методы для работы с порядком | Нет | Да (movetoend, popitem) | Нет |
| Сохранение порядка при равенстве | Нет | Да | Нет |
| Память | Больше обычного dict | Больше обычного dict | Базовый уровень |
Когда использовать:
- defaultdict: когда вы часто обращаетесь к несуществующим ключам и хотите получать значение по умолчанию вместо ошибки
- OrderedDict: когда важны специальные методы управления порядком или равенство с учетом порядка
- Стандартный dict: для большинства случаев в Python 3.7+, если не нужны специфические возможности defaultdict или OrderedDict
deque: оптимизированные двусторонние очереди
Deque (произносится как "дек") — сокращение от "double-ended queue" (двусторонняя очередь). Это обобщение стеков и очередей, оптимизированное для быстрых операций добавления и удаления с обоих концов. В отличие от списков, которые оптимизированы для быстрого доступа по индексу, deque обеспечивает O(1) сложность для операций append и pop с обоих концов. 🔄
Марина Соколова, разработчик алгоритмов
В процессе разработки системы анализа временных рядов я столкнулась с необходимостью поддерживать "скользящее окно" значений. Начала с обычного списка, но заметила значительное падение производительности при обработке миллионов точек данных:
PythonСкопировать кодwindow = [] max_size = 1000 for value in data_stream: window.append(value) if len(window) > max_size: window.pop(0) # O(n) операция для списков! # Обработка окна...Каждый вызов
pop(0)требовал перемещения всех оставшихся элементов, что давало квадратичную сложность. После замены списка на deque, обработка ускорилась в 50 раз:PythonСкопировать кодfrom collections import deque window = deque(maxlen=1000) for value in data_stream: window.append(value) # Обработка окна...Deque автоматически удаляет старые значения при достижении максимальной длины, и делает это за O(1). Это превратило непрактичный алгоритм в высокопроизводительное решение.
Создание и использование deque:
from collections import deque
# Создание пустой очереди
d = deque()
# Создание очереди с начальными данными
d = deque([1, 2, 3, 4, 5])
# Создание очереди с ограниченным размером
d = deque(maxlen=3) # Автоматически удаляет старые элементы при достижении лимита
# Операции с правым концом (как в обычном списке)
d.append(1) # добавление в конец
d.pop() # удаление и возврат последнего элемента
# Операции с левым концом (быстрее, чем в списке)
d.appendleft(0) # добавление в начало
d.popleft() # удаление и возврат первого элемента
# Другие полезные методы
d.extend([4, 5, 6]) # добавление нескольких элементов справа
d.extendleft([3, 2, 1]) # добавление нескольких элементов слева (в обратном порядке!)
d.rotate(1) # циклический сдвиг вправо
d.rotate(-2) # циклический сдвиг влево
d.clear() # очистка очереди
Преимущества deque перед списком:
- O(1) сложность операций append/pop с обоих концов (vs O(n) для операций в начале списка)
- Потокобезопасность: атомарные операции appendleft() и popleft()
- Возможность установки максимальной длины
- Эффективные операции rotate() и reverse()
Практические применения deque:
- Скользящие окна: хранение последних N элементов потока данных
- Очереди задач: FIFO (First In, First Out) с возможностью добавления приоритетных задач в начало
- Алгоритмы обхода графов: BFS (поиск в ширину) с эффективной реализацией очереди
- Историю отмены действий: хранение последовательности операций с возможностью отмены/повтора
- Буферизация данных: эффективное управление потоком данных с обоих концов
Сравнение производительности для операций в начале коллекции:
import time
from collections import deque
# Сравним операции вставки в начало списка и deque
n = 100000
# Измеряем для списка
start = time.time()
lst = []
for i in range(n):
lst.insert(0, i) # вставка в начало списка O(n)
list_time = time.time() – start
# Измеряем для deque
start = time.time()
d = deque()
for i in range(n):
d.appendleft(i) # вставка в начало deque O(1)
deque_time = time.time() – start
print(f"Список: {list_time:.4f} секунд")
print(f"Deque: {deque_time:.4f} секунд")
print(f"Deque быстрее в {list_time/deque_time:.1f} раз")
# Вывод (примерно):
# Список: 4.7251 секунд
# Deque: 0.0151 секунд
# Deque быстрее в 312.9 раз
namedtuple и ChainMap: практическое применение в проектах
Для завершения нашего обзора модуля collections, рассмотрим два мощных, но часто недооцененных инструмента: namedtuple и ChainMap. Эти классы могут значительно улучшить структуру и читаемость вашего кода при правильном применении. 🏗️
namedtuple: именованные поля в легковесных структурах данных
namedtuple — это фабричная функция, создающая подклассы tuple с именованными полями. Это позволяет обращаться к элементам по имени (через точку) вместо индекса, что делает код более понятным и самодокументируемым.
from collections import namedtuple
# Определение класса Point
Point = namedtuple('Point', ['x', 'y'])
# Создание экземпляра
p = Point(11, 22)
# Доступ по имени поля
print(p.x, p.y) # 11 22
# Также работает доступ по индексу (как обычный кортеж)
print(p[0], p[1]) # 11 22
# Распаковка (как обычный кортеж)
x, y = p
print(x, y) # 11 22
# Неизменяемость (как обычный кортеж)
# p.x = 33 # Вызывает AttributeError
Дополнительные возможности namedtuple:
# Альтернативные способы определения полей
Point = namedtuple('Point', 'x y') # через строку с пробелами
Point = namedtuple('Point', 'x, y') # через строку с запятыми
# Создание из словаря
d = {'x': 11, 'y': 22}
p = Point(**d)
# Получение словаря из namedtuple
d = p._asdict() # OrderedDict([('x', 11), ('y', 22)])
# Создание новой версии с изменёнными значениями
p2 = p._replace(x=33) # Point(x=33, y=22)
# Получение значений по умолчанию
Point = namedtuple('Point', ['x', 'y'], defaults=[0, 0])
p = Point() # Point(x=0, y=0)
p = Point(1) # Point(x=1, y=0)
Преимущества namedtuple перед обычными кортежами и классами:
- Именованные поля делают код более читаемым и понятным
- Компактное представление в памяти (меньше, чем у обычных классов)
- Неизменяемость обеспечивает безопасность при передаче данных
- Совместимость со всеми функциями, ожидающими обычные кортежи
- Автоматические методы repr, eq и др.
ChainMap: объединение нескольких словарей
ChainMap — это класс, позволяющий объединить несколько словарей или других отображений в единое представление. Поиск ключей осуществляется по цепочке словарей в порядке их добавления, что позволяет реализовать логику наследования или приоритетов.
from collections import ChainMap
# Несколько словарей
defaults = {'theme': 'default', 'language': 'en', 'show_errors': True}
user_settings = {'theme': 'dark'}
# Объединение в ChainMap
settings = ChainMap(user_settings, defaults)
# Поиск ключей в первом словаре, где они найдены
print(settings['theme']) # 'dark' (из user_settings)
print(settings['language']) # 'en' (из defaults)
# Изменения затрагивают только первый словарь
settings['language'] = 'fr' # Изменяется только user_settings
print(user_settings) # {'theme': 'dark', 'language': 'fr'}
print(defaults) # Остаётся неизменным
# Проверка наличия ключа во всей цепочке
print('theme' in settings) # True
# Получение всех ключей из всех словарей (без дубликатов)
print(list(settings.keys())) # ['theme', 'language', 'show_errors']
Полезные методы ChainMap:
- maps — список подлежащих словарей в порядке поиска
- new_child(m=None) — создаёт новый ChainMap с новым словарём m (или пустым) в начале цепочки
- parents — новый ChainMap, содержащий все словари кроме первого
# Добавление нового уровня с новыми настройками
session_settings = {'theme': 'light'}
new_settings = settings.new_child(session_settings)
print(new_settings['theme']) # 'light'
# Удаление верхнего уровня настроек
parent_settings = new_settings.parents
print(parent_settings['theme']) # 'dark' (вернулись к user_settings)
Практические применения namedtuple и ChainMap:
| Класс | Сценарий использования | Пример применения |
|---|---|---|
| namedtuple | Возврат нескольких значений из функции | Result = namedtuple('Result', ['value', 'error']) |
| namedtuple | Замена простых классов данных | Point, Rectangle, User без методов |
| namedtuple | CSV/JSON структурированные данные | Записи данных с фиксированной структурой |
| ChainMap | Многоуровневая конфигурация | Системные, пользовательские, сеансовые настройки |
| ChainMap | Контексты шаблонизатора | Переменные разных уровней видимости в шаблонах |
| ChainMap | Реализация вложенных областей видимости | Поиск переменных как в языках программирования |
Изучение модуля collections — не просто расширение инструментария, а стратегическая инвестиция в качество вашего кода. Правильно подобранная структура данных может уменьшить сложность алгоритма на порядки, сделать код более понятным и снизить вероятность ошибок. Эти специализированные контейнеры разработаны для решения конкретных проблем, и теперь, когда вы знаете их возможности, вы сможете выбирать оптимальный инструмент для каждой задачи, вместо того чтобы "забивать гвозди микроскопом".