Представьте ситуацию: вам нужно хранить в словаре в качестве значений что-нибудь изменяемое, скажем, списки. И вот в процессе работы с таким словарём вы попадаете в ситуацию, когда у вас есть ключ и элемент для добавления в список-значение, но — вот ведь незадача — ключ пока в словаре не представлен. Приходится писать подобный код:
if key not in dictionary:
dictionary[key] = [] # инициализируем список
dictionary[key].append(value) # изменяем список
Подобная ситуация встречается не так уж и редко. Это понимали и авторы стандартной библиотеки Python и дали словарю метод setdefault
. Вышеупомянутый код можно переписать с использованием этого метода:
dictionary.setdefault(key, []).append(value)
Компактно и лаконично! Но что же делает метод setdefault
? Метод принимает ключ и значение по умолчанию и возвращает ссылку на значение в словаре, связанное с указанным ключом. А если ключ в словаре отсутствует, то метод помещает по ключу то самое значение по умолчанию и возвращает ссылку на него!
В примере выше значением по умолчанию выступает пустой список
[]
.
defaultdict
В стандартной поставке Python присутствует модуль collections. Этот модуль, помимо прочего, предоставляет тип defaultdict
. defaultdict
— это во всех отношениях обычный словарь, но обладающий одним уникальным свойством: там, где словарь "ругается" на отсутствие ключа, defaultdict сам возвращает значение по умолчанию. Давайте рассмотрим пример:
from collections import defaultdict
d = defaultdict(int)
d['a'] += 5
d['b'] = d['c'] + 10
d # defaultdict(<class 'int'>, {'a': 5, 'c': 0, 'b': 10})
При создании словаря я указал в качестве аргумента функцию int
. Если эту функцию вызвать без аргументов, то она вернёт 0
и именно этот вызов внутри словаря d
и происходит всякий раз, когда нужно получить значение для несуществующего ключа. Поэтому d['a'] += 5
даёт в итоге 5
, т.к. сначала для ключа 'a'
создаётся начальное значение (делается вызов int()
и получается 0
), а потом к нему прибавляется 5
. В строчке d['b'] = d['c'] + 10
создаются значения для ключей 'b'
и 'c'
и затем уже по ключу 'b'
записывается сумма 0 + 10
.
Вот ещё один пример — на этот раз с самодельной функцией-инициализатором:
def new_value():
return 'foo'
x = defaultdict(new_value)
x[1] # 'foo'
x['bar'] # 'foo'
x # defaultdict(<function new_value at 0x7f2232cf5a60>, {1: 'foo', 'bar': 'foo'})
Если отбросить немного непонятное упоминание функции-инициализатора, видно, что по всем ключам, по которым я обращался к содержимому словаря, теперь записаны строки 'foo'
.
defaultdict
от обычного словаря c setdefault
Зачем же иметь оба способа, если они настолько похожи, спросите вы. Но давайте сравним эти две строки:
a.setdefault(key, []).append…
# vs
b[key].append…
# b это defaultdict(list)
Строки очень похожи, но если во втором случае новый список создаётся только тогда, когда ключ не будет найден, то в первой строчке объект пустого списка будет создаваться каждый раз. Так как значения аргументов всегда вычисляются до того как будет вызвана функция, в данном случае setdefault(key, [])
, конкретно затратами на создание пустого списка можно пренебречь. Однако, если вдруг затраты на создание значения по умолчанию окажутся велики, скажем, каждое создание потребует хождения в базу данных, то вариант с defaultdict сразу же окажется гораздо более предпочтителен!
Зачем же вообще использовать setdefault? Например для того, чтобы по разным ключам инициализировать разные значения! Т.к. значение по умолчанию передаётся каждый раз, мы можем по разным ключам хранить даже разные типы данных. С defaultdict у нас нет контроля над тем, какие значения по каким ключам класть: функция-инициализатор вызывается каждый раз одна и та же и ключ в неё не передаётся.
Наконец всегда остаются редкие случаи, когда и defaultdict
не подходит, потому что нужно инициализировать значения по-разному, но не подходит и setdefault
— новые значения неизменяемы и их не получится изменить по возвращаемой ссылке. Пример такого случая (вместе с решением задачи ненахождения ключа):
x['count'] = x.get('count', 0) + 1
x['path'] = x.get('path', '') + '/' + dir
Да, здесь присутствуют лишние хождения по одному и тому же ключу, но сам код читается неплохо и в данной ситуации, можно сказать, оптимален!
Вам ответят команда поддержки Хекслета или другие студенты.
Выделите текст, нажмите ctrl + enter и отправьте его нам. В течение нескольких дней мы исправим ошибку или улучшим формулировку.
Загляните в раздел «Обсуждение»:
Статья «Ловушки обучения»
Вебинар «Как самостоятельно учиться»
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно.
Наши выпускники работают в компаниях:
С нуля до разработчика. Возвращаем деньги, если не удалось найти работу.
Зарегистрируйтесь или войдите в свой аккаунт