← Назад к курсу

Изучаем модуль collections в Python

Модуль collections предоставляет специализированные контейнерные типы данных, которые являются альтернативами встроенным контейнерам (list, dict, set, tuple). Эти типы оптимизированы для конкретных задач и часто более эффективны.

Содержание

  1. Counter
  2. defaultdict
  3. deque
  4. namedtuple
  5. OrderedDict
  6. ChainMap

1. Counter

Счетчик (Counter) — это подкласс словаря для подсчета хешируемых объектов.

Основное использование:

from collections import Counter

# Создание Counter
words = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple']
word_count = Counter(words)
print(word_count)  # Counter({'apple': 3, 'banana': 2, 'orange': 1})

# Подсчет символов в строке
char_count = Counter("abracadabra")
print(char_count)  # Counter({'a': 5, 'b': 2, 'r': 2, 'c': 1, 'd': 1})

Полезные методы:

# most_common() - n самых частых элементов
print(word_count.most_common(2))  # [('apple', 3), ('banana', 2)]

# elements() - итератор по элементам
print(list(word_count.elements()))  # ['apple', 'apple', 'apple', 'banana', 'banana', 'orange']

# Арифметические операции
c1 = Counter(a=3, b=1)
c2 = Counter(a=1, b=2)
print(c1 + c2)  # Counter({'a': 4, 'b': 3})
print(c1 - c2)  # Counter({'a': 2})

2. defaultdict

defaultdict — это подкласс словаря, который вызывает factory-функцию для отсутствующих ключей.

Пример:

from collections import defaultdict

# Создание defaultdict с list как factory-функцией
dd = defaultdict(list)

# При обращении к отсутствующему ключу создается пустой список
dd['fruits'].append('apple')
dd['fruits'].append('banana')
dd['vegetables'].append('carrot')

print(dd)
# defaultdict(<class 'list'>, {'fruits': ['apple', 'banana'], 'vegetables': ['carrot']})

# С lambda-функцией
dd_int = defaultdict(lambda: 0)
dd_int['count'] += 1
print(dd_int['count'])  # 1
print(dd_int['missing'])  # 0 (автоматически создается)

Практический пример:

# Группировка слов по длине
words = ['apple', 'bat', 'cat', 'orange', 'dog', 'elephant']
word_by_length = defaultdict(list)

for word in words:
    word_by_length[len(word)].append(word)

print(dict(word_by_length))
# {5: ['apple', 'orange'], 3: ['bat', 'cat', 'dog'], 8: ['elephant']}

3. deque

deque (двусторонняя очередь) — оптимизированная структура для быстрого добавления/удаления с обоих концов.

Основные операции:

from collections import deque

# Создание deque
d = deque([1, 2, 3])
print(d)  # deque([1, 2, 3])

# Добавление элементов
d.append(4)      # справа
d.appendleft(0)  # слева
print(d)  # deque([0, 1, 2, 3, 4])

# Удаление элементов
d.pop()          # справа
d.popleft()      # слева
print(d)  # deque([1, 2, 3])

# Вращение
d.rotate(1)      # вправо на 1
print(d)  # deque([3, 1, 2])
d.rotate(-1)     # влево на 1
print(d)  # deque([1, 2, 3])

# Ограничение максимальной длины
d_max = deque(maxlen=3)
for i in range(5):
    d_max.append(i)
    print(d_max)
# deque([0], maxlen=3)
# deque([0, 1], maxlen=3)
# deque([0, 1, 2], maxlen=3)
# deque([1, 2, 3], maxlen=3) - вытеснился 0
# deque([2, 3, 4], maxlen=3) - вытеснился 1

4. namedtuple

namedtuple создает подкласс tuple с именованными полями.

Создание и использование:

from collections import namedtuple

# Создание namedtuple
Point = namedtuple('Point', ['x', 'y'])
Color = namedtuple('Color', 'red green blue')

# Создание экземпляров
p = Point(10, 20)
c = Color(255, 0, 0)

print(p.x, p.y)  # 10 20
print(c.red)     # 255

# Доступ по индексу (как обычный tuple)
print(p[0])  # 10

# Неизменяемость (как tuple)
# p.x = 100  # Ошибка: AttributeError

# Дополнительные возможности
print(p._asdict())  # {'x': 10, 'y': 20}
print(p._fields)    # ('x', 'y')

# Замена значений (создает новый экземпляр)
p2 = p._replace(x=100)
print(p2)  # Point(x=100, y=20)

С параметром defaults (Python 3.7+):

Person = namedtuple('Person', ['name', 'age', 'city'], defaults=['Moscow', 25])
p1 = Person('Alice')  # age=25, city='Moscow' по умолчанию
print(p1)  # Person(name='Alice', age=25, city='Moscow')

5. OrderedDict

OrderedDict — словарь, который сохраняет порядок добавления элементов (в Python 3.7+ обычные dict тоже сохраняют порядок, но OrderedDict имеет дополнительные методы).

Особенности:

from collections import OrderedDict

# Создание OrderedDict
od = OrderedDict()
od['a'] = 1
od['c'] = 3
od['b'] = 2

print(od)  # OrderedDict([('a', 1), ('c', 3), ('b', 2)])

# Перемещение элемента в конец
od.move_to_end('a')
print(od)  # OrderedDict([('c', 3), ('b', 2), ('a', 1)])

# Перемещение элемента в начало
od.move_to_end('b', last=False)
print(od)  # OrderedDict([('b', 2), ('c', 3), ('a', 1)])

# popitem() - удаление последнего/первого элемента
last = od.popitem()
print(last)  # ('a', 1)
first = od.popitem(last=False)
print(first)  # ('b', 2)

6. ChainMap

ChainMap объединяет несколько словарей в одно представление.

Использование:

from collections import ChainMap

dict1 = {'a': 1, 'b': 2}
dict2 = {'b': 3, 'c': 4}
dict3 = {'d': 5}

# Создание ChainMap
chain = ChainMap(dict1, dict2, dict3)

# Поиск идет по цепочке слева направо
print(chain['a'])  # 1 (из dict1)
print(chain['b'])  # 2 (из dict1, первый найденный)
print(chain['c'])  # 4 (из dict2)
print(chain['d'])  # 5 (из dict3)

# Изменения затрагивают только первый словарь
chain['b'] = 100
print(dict1)  # {'a': 1, 'b': 100}
print(dict2)  # {'b': 3, 'c': 4} (не изменился)

# Добавление нового контекста
new_chain = chain.new_child({'e': 6})
print(new_chain)  # ChainMap({'e': 6}, {'a': 1, 'b': 100}, {'b': 3, 'c': 4}, {'d': 5})

Практический пример (конфигурация):

defaults = {'theme': 'light', 'language': 'en'}
user_settings = {'theme': 'dark'}
system_env = {'language': 'ru'}

config = ChainMap(user_settings, system_env, defaults)
print(config['theme'])    # 'dark' (из user_settings)
print(config['language']) # 'ru' (из system_env)

Практические задачи

Задача 1: Анализ текста

from collections import Counter
import re

def analyze_text(text):
    # Приведение к нижнему регистру и удаление знаков препинания
    words = re.findall(r'\b\w+\b', text.lower())
    word_counter = Counter(words)
    
    print("10 самых частых слов:")
    for word, count in word_counter.most_common(10):
        print(f"{word}: {count}")
    
    print(f"\nВсего уникальных слов: {len(word_counter)}")
    print(f"Всего слов: {sum(word_counter.values())}")

text = "Hello world! Hello Python. Python is great. World of Python."
analyze_text(text)

Задача 2: Кэш LRU (Least Recently Used)

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)
        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)

# Использование
cache = LRUCache(2)
cache.put(1, 'a')
cache.put(2, 'b')
print(cache.get(1))  # 'a'
cache.put(3, 'c')    # ключ 2 удаляется (превышение capacity)
print(cache.get(2))  # -1 (не найден)

Сводная таблица

Коллекция Назначение Ключевые особенности
Counter Подсчет элементов Подсчет частот, арифметические операции
defaultdict Словарь со значениями по умолчанию Автоматическое создание отсутствующих ключей
deque Двусторонняя очередь Быстрые O(1) операции с обоих концов
namedtuple Именованный кортеж Чтение по именам, неизменяемость
OrderedDict Упорядоченный словарь Сохранение порядка, move_to_end
ChainMap Цепочка словарей Объединение словарей, поиск по цепочке

Заключение

Модуль collections предоставляет мощные и оптимизированные структуры данных, которые упрощают решение многих задач. Выбирайте подходящую коллекцию в зависимости от ваших потребностей:

  • Для подсчета элементов → Counter
  • Для группировки данных → defaultdict
  • Для очередей и стеков → deque
  • Для читаемых структур данных → namedtuple
  • Для упорядоченных словарей с дополнительными операциями → OrderedDict
  • Для объединения контекстов → ChainMap