Другое

defaultdict vs обычный словарь в Python: ключевые различия

Узнайте основные отличия между collections.defaultdict и обычными словарями Python. Поймите, как работают функции int и list в defaultdict с примерами.

Какова разница между collections.defaultdict и обычным словарём Python? В приведённых ниже примерах, какую роль играют параметры int и list в defaultdict?

Пример 1:

python
>>> from collections import defaultdict
>>> s = 'mississippi'
>>> d = defaultdict(int)
>>> for k in s:
...     d[k] += 1
...
>>> d.items()
dict_items([('m', 1), ('i', 4), ('s', 4), ('p', 2)])

Пример 2:

python
>>> s = [('yellow', 1), ('blue', 2), ('yellow', 3), ('blue', 4), ('red', 1)]
>>> d = defaultdict(list)
>>> for k, v in s:
...     d[k].append(v)
...
>>> d.items()
[('blue', [2, 4]), ('red', [1]), ('yellow', [1, 3])]

A collections.defaultdict отличается от обычного словаря Python прежде всего тем, как он обрабатывает отсутствующие ключи – он никогда не генерирует KeyError и вместо этого автоматически создаёт значения по умолчанию, используя фабричную функцию, заданную при инициализации. Это устраняет необходимость вручную проверять наличие ключа и инициализировать значения, делая код чище и более читаемым.

Содержание

Понимание основных различий

Основное различие между collections.defaultdict и обычным словарём заключается в том, как они обрабатывают доступ к отсутствующим ключам. Согласно Stack Overflow, «defaultdict будет «устанавливать» значение по умолчанию, если ключ ещё не был задан». При попытке доступа к ключу, которого нет в обычном словаре, Python генерирует KeyError:

python
>>> normal_dict = {}
>>> normal_dict['missing_key']
KeyError: 'missing_key'

В отличие от этого, как объясняет Code Underscored, «defaultdict никогда не генерирует KeyError», потому что автоматически предоставляет значение по умолчанию для несуществующих ключей.

Ключевое архитектурное различие состоит в том, что defaultdict «позволяет вызватьу указать значение по умолчанию заранее при инициализации контейнера» [источник: PyMotW]. Такой подход с фабричной функцией обеспечивает последовательное, предсказуемое поведение для отсутствующих ключей во всём коде.

Как работают фабричные функции

Параметры int и list в примерах с defaultdict являются фабричными функциями – вызываемыми объектами, которые возвращают значения по умолчанию при первом доступе к ключу. Это не просто типы, а функции, которые вызываются для получения значения по умолчанию.

Фабричная функция int

Когда вы используете defaultdict(int), вы указываете, что фабричная функция должна быть классом int. Как объясняет GeeksforGeeks, «int() возвращает 0, поэтому отсутствующие ключи будут иметь значение по умолчанию 0». Это идеально подходит для операций подсчёта:

python
>>> from collections import defaultdict
>>> d = defaultdict(int)
>>> print(d['missing_key'])  # Автоматически создаёт ключ со значением 0
0

В Примере 1 это позволяет чисто подсчитывать частоты символов:

python
>>> s = 'mississippi'
>>> d = defaultdict(int)
>>> for k in s:
...     d[k] += 1  # Отсутствующие ключи автоматически начинаются с 0

Фабричная функция list

Аналогично, при использовании defaultdict(list) фабричной функцией является класс list. Согласно официальной документации Python, «list() возвращает пустой список», поэтому отсутствующие ключи автоматически получают пустой список в качестве значения. Это позволяет элегантно группировать данные:

python
>>> d = defaultdict(list)
>>> print(d['missing_key'])  # Автоматически создаёт ключ со значением []
[]

В Примере 2 это создаёт естественный механизм группировки:

python
>>> s = [('yellow', 1), ('blue', 2), ('yellow', 3), ('blue', 4), ('red', 1)]
>>> d = defaultdict(list)
>>> for k, v in s:
...     d[k].append(v)  # Отсутствующие ключи автоматически получают пустые списки

Типичные фабричные функции включают:

  • int → возвращает 0 (для подсчётов)
  • list → возвращает [] (для группировки)
  • set → возвращает set() (для сбора уникальных элементов)
  • dict → возвращает {} (для вложенных словарей)
  • Пользовательские функции или лямбда‑выражения для любых значений по умолчанию

Практические примеры и случаи использования

Давайте разберём, почему эти фабричные функции так мощны, через приведённые примеры:

Пример 1: Подсчёт частоты символов

python
>>> from collections import defaultdict
>>> s = 'mississippi'
>>> d = defaultdict(int)
>>> for k in s:
...     d[k] += 1
>>> d.items()
dict_items([('m', 1), ('i', 4), ('s', 4), ('p', 2)])

Без defaultdict пришлось бы писать ручные проверки:

python
normal_dict = {}
for k in 'mississippi':
    if k not in normal_dict:
        normal_dict[k] = 0
    normal_dict[k] += 1

Фабричная функция int устраняет эту шаблонную логику, гарантируя, что каждый отсутствующий ключ начинается с 0.

Пример 2: Группировка данных

python
>>> s = [('yellow', 1), ('blue', 2), ('yellow', 3), ('blue', 4), ('red', 1)]
>>> d = defaultdict(list)
>>> for k, v in s:
...     d[k].append(v)
>>> d.items()
[('blue', [2, 4]), ('red', [1]), ('yellow', [1, 3])]

Как отмечает Real Python, «одним из самых распространённых применений типа defaultdict является установка default_factory в list и последующее построение словаря, который отображает ключи на списки значений». Этот шаблон чрезвычайно полезен для:

  • Группировки данных по категориям
  • Создания обратных индексов
  • Сборки значений по ключу
  • Создания многозначных отображений

Другие распространённые фабричные функции

Вы можете использовать любой вызываемый объект в качестве фабричной функции:

python
# Пользовательское значение по умолчанию
>>> def default_value():
...     return 'not found'
>>> d = defaultdict(default_value)
>>> d['missing']  # Возвращает 'not found'

# Лямбда для конкретных значений по умолчанию
>>> d = defaultdict(lambda: 1)  # Отсутствующие ключи по умолчанию 1
>>> d['missing']  # Возвращает 1

Проблемы производительности

Хотя defaultdict делает код чище, существуют компромиссы по производительности. Согласно Stack Overflow, «литерал словаря ({}) создаётся намного быстрее, чем вызов defaultdict(...) (глобальный поиск, стековый вызов, инвокация), что искажает результаты».

Однако после создания операции с defaultdict могут быть быстрее, чем стандартные словари с ручными проверками ключей, потому что «__getitem__ defaultdict реализован на C, который напрямую проверяет наличие ключа. Если ключ отсутствует, он вызывает фабричную функцию (например, list()) и вставляет результат в словарь – всё это в одной низкоуровневой операции» [источник: Python Tutorials].

Разница в производительности наиболее заметна в сценариях с большим количеством обращений к отсутствующим ключам, где defaultdict устраняет накладные расходы повторяющихся проверок key in dict.


Когда использовать defaultdict вместо обычного словаря

Используйте collections.defaultdict, когда:

  • У вас предсказуемые значения по умолчанию для отсутствующих ключей
  • Вы выполняете операции подсчёта или группировки
  • Вы хотите более чистый, читаемый код
  • Вы работаете с данными, которые естественно имеют состояния по умолчанию

Оставайтесь с обычными словарями, когда:

  • Вы хотите явно обрабатывать KeyError для отсутствующих ключей
  • Производительность при создании словаря критична
  • Разные ключи требуют разных значений по умолчанию
  • Нужно различать действительно отсутствующие ключи и ключи со значениями None/0

Как предлагает DataFlair, defaultdict превосходен в «ситуациях, когда вы хотите избежать проверки существования ключей перед их использованием», что делает его особенно ценным для задач обработки данных, подсчёта и группировки.

Источники

  1. Stack Overflow - различие между dict и collections.defaultdict
  2. Code Underscored - defaultdict vs dictionaries
  3. GeeksforGeeks - defaultdict в Python
  4. Python Documentation - модуль collections
  5. Real Python - Использование defaultdict
  6. PyMotW - примеры defaultdict
  7. DataFlair - урок по defaultdict
  8. Stack Overflow - производительность OrderedDict vs defaultdict vs dict

Заключение

Ключевые различия между collections.defaultdict и обычными словарями сводятся к автоматическому созданию значений по умолчанию и более чистому коду. Параметры int и list служат фабричными функциями, которые определяют, какое значение по умолчанию создать при первом доступе к ключу – int() возвращает 0 для подсчётов, а list() возвращает пустой список для операций группировки.

Основные выводы:

  • defaultdict устраняет KeyError, автоматически создавая значения по умолчанию
  • Фабричные функции, такие как int и list, определяют способ создания значений по умолчанию
  • Это приводит к более чистому, читаемому коду для операций подсчёта и группировки
  • Обычные словари лучше, когда нужна явная обработка отсутствующих ключей
  • Выбор влияет как на читаемость кода, так и на характеристики производительности

Понимание этих различий помогает выбрать правильный инструмент для конкретных задач обработки данных, будь то подсчёт частот, группировка данных или построение сложных структур.

Авторы
Проверено модерацией
Модерация