collections в Python: defaultdict, Counter и deque — практическое руководство с примерами

collections в Python: defaultdict, Counter и deque — практическое руководство с примерами

collections в Python: defaultdict, Counter и deque — практическое руководство с примерами

Модуль collections в Python — это готовые структуры данных с оптимизированным поведением. В этой статье мы разберём три самых полезных инструмента: defaultdict, Counter и deque. Если вы ищете запрос вроде «collections в Python: defaultdict, Counter, deque», вы по адресу — ниже только практика и понятные примеры.

Когда использовать collections вместо list и dict

  • Вы постоянно проверяете «есть ли ключ в словаре?» — вероятно, вам нужен defaultdict.
  • Вы считаете частоты элементов (слова, статусы, клики) — это работа для Counter.
  • Вам нужна очередь с быстрыми вставками/удалениями слева и справа — используйте deque.
  • 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] создаст ключ, что может «раздувать» словарь при чтении.
  • Перед сериализацией в JSON приводите к 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))
    

    Лучшие практики и рекомендации

  • Читабельность важнее трюков. Не превращайте простой код в «головоломку» только ради использования collections.
  • API‑границы. Избегайте возврата defaultdict наружу — приводите к dict, чтобы не удивлять клиентов функции.
  • Производительность. Для подсчётов больших массивов строк Counter и defaultdict(int) работают заметно быстрее, чем «ручная» логика с проверками ключей.
  • deque с maxlen — простой способ сделать «буфер последних N событий» без лишнего кода и утечек памяти.
  • Тесты. Для 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 с Нуля до Гуру» →

    Источник

    НЕТ КОММЕНТАРИЕВ

    Оставить комментарий