defaultdict
: эволюция словаря без условий
В Python существует множество внешних библиотек, но даже при правильном понимании стандартной библиотеки можно писать достаточно мощный код для практики. В этой статье мы глубоко рассмотрим collections.defaultdict
.
Вы поймёте, когда, почему и как использовать defaultdict
, выходя за рамки простого введения в концепцию.
Тем, кто интересуется первым классом в серии collections, классов Counter
, я рекомендую прочитать предыдущую статью.
Стандартная библиотека Python ① - collections.Counter
1. Основная концепция: Что такое defaultdict
?
defaultdict
- это специальный подкласс словаря (dict), входящий в модуль collections
стандартной библиотеки Python. Когда вы обращаетесь к ключу, которого нет в обычном словаре, возникает KeyError
, но defaultdict
позволяет указать функцию для автоматического создания значения по умолчанию (фабричную функцию), что делает код намного чище и помогает избежать ошибок.
2. Основное использование
from collections import defaultdict
d = defaultdict(int)
d['apple'] += 1
print(d) # defaultdict(<class 'int'>, {'apple': 1})
Здесь int()
возвращает значение по умолчанию 0
. При обращении к несуществующему ключу 'apple'
автоматически создаётся 0
без возникновения KeyError
, и затем выполняется +1
.
3. Примеры различных значений по умолчанию
from collections import defaultdict
# Значение по умолчанию: 0 (int)
counter = defaultdict(int)
counter['a'] += 1
print(counter) # defaultdict(<class 'int'>, {'a': 1})
# Значение по умолчанию: пустой список
group = defaultdict(list)
group['fruit'].append('apple')
group['fruit'].append('banana')
print(group) # defaultdict(<class 'list'>, {'fruit': ['apple', 'banana']})
# Значение по умолчанию: пустое множество
unique_tags = defaultdict(set)
unique_tags['tags'].add('python')
unique_tags['tags'].add('coding')
print(unique_tags) # defaultdict(<class 'set'>, {'tags': {'python', 'coding'}})
# Значение по умолчанию: пользовательское начальное значение
fixed = defaultdict(lambda: 100)
print(fixed['unknown']) # 100
4. Практические примеры
1. Подсчёт частоты слов
words = ['apple', 'banana', 'apple', 'orange', 'banana']
counter = defaultdict(int)
for word in words:
counter[word] += 1
print(counter)
# defaultdict(<class 'int'>, {'apple': 2, 'banana': 2, 'orange': 1})
👉 Counter vs defaultdict
Поскольку collections.Counter()
более специализирован для подсчёта слов, в случаях, когда необходима статистика или анализ рангов, предпочтительнее использовать Counter
. Однако для простого суммирования, как при накоплении счётов, defaultdict(int)
также вполне достаточно и может использоваться с простотой.
2. Группировка логов
logs = [
('2024-01-01', 'INFO'),
('2024-01-01', 'ERROR'),
('2024-01-02', 'DEBUG'),
]
grouped = defaultdict(list)
for date, level in logs:
grouped[date].append(level)
print(grouped)
# defaultdict(<class 'list'>, {'2024-01-01': ['INFO', 'ERROR'], '2024-01-02': ['DEBUG']})
3. Упорядочение уникальных тегов
entries = [
('post1', 'python'),
('post1', 'coding'),
('post1', 'python'),
]
tags = defaultdict(set)
for post, tag in entries:
tags[post].add(tag)
print(tags)
# defaultdict(<class 'set'>, {'post1': {'python', 'coding'}})
5. Важные моменты
defaultdict
хранит генератор значений по умолчанию внутри, поэтому при использованииrepr()
может иметь другой вид, чем обычныйdict
.- Это может вызвать проблемы при сериализации JSON. Лучше всего преобразовать его в
dict(d)
перед обработкой. - Значения по умолчанию создаются только при доступе через
[]
. При доступе черезget()
они не создаются.
from collections import defaultdict
d = defaultdict(list)
print(d.get('missing')) # None
print(d['missing']) # []
6. Когда это полезно? – 3 критических преимущества defaultdict
defaultdict
повышает читаемость, поддерживаемость и безопасность в ситуациях, когда часто используются паттерны dict
+ условие
. Особенно в следующих трёх ситуациях осознаете: «Да, здесь определённо лучше использовать defaultdict!».
6-1. Код агрегирования без условий
from collections import defaultdict
# Обычный dict
counts = {}
for item in items:
if item not in counts:
counts[item] = 0
counts[item] += 1
# defaultdict
counts = defaultdict(int)
for item in items:
counts[item] += 1
✔ Устранение условного оператора делает код более лаконичным и снижает вероятность ошибок.
✔ Особенно подходит для обработки больших данных, таких как анализ логов и подсчёт слов.
6-2. Замена setdefault
при накоплении списков/множеств
from collections import defaultdict
# Обычный dict
posts = {}
for tag, post in data:
if tag not in posts:
posts[tag] = []
posts[tag].append(post)
# defaultdict
posts = defaultdict(list)
for tag, post in data:
posts[tag].append(post)
✔ Намного более интуитивно и аккуратно, даже в цикле.
✔ Структура оптимизирована для группировки данных.
6-3. Автоматизация инициализации вложенных словарей
# Обычный словарь
matrix = {}
if 'x' not in matrix:
matrix['x'] = {}
matrix['x']['y'] = 10
# Вложенный defaultdict
matrix = defaultdict(lambda: defaultdict(int)) # При отсутствии ключа автоматически создаётся defaultdict(int)
matrix['x']['y'] += 10
✔ Упрощает создание вложенных структур в многомерных словарях.
✔ Идеально подходит для добычи данных, парсинга и хранения древовидных структур.
lambda: defaultdict(int)
имеет структуру, возврат которойdefaultdict(int)
создаётся автоматически при отсутствии ключа.
7. Резюме
collections.defaultdict
может выглядеть для новичков как простая расширенная версия dict
, но по мере его использования вы поймёте, что это структурный инструмент, делающий код более ясным и безопасным.
- Вы можете использовать словарь, не беспокоясь о
KeyError
. - Можно группировать и накапливать данные без условий.
- Можно интуитивно строить вложенные словари.
# Пример безопасной и лаконичной обработки с помощью defaultdict
salaries = defaultdict(int)
for dept, amount in records:
salaries[dept] += amount
Если вы можете избежать ошибок всего одной строкой кода + улучшить читаемость + поддерживаемость, то
defaultdict
- это не просто полезная функция, а основной инструмент мышления на Python.
Следующей темой будет pathlib
.
Это современный способ, который сделает код для работы с файлами/каталогами объектно-ориентированным, и будет полезен как новичкам, так и более опытным пользователям. Разработчики, знакомые с os.path
, почувствуют: «Ух ты! Это гораздо проще, чем os.path!». Ожидайте следующей части!
댓글이 없습니다.