setdefault: значение при первом обращении
setdefault возвращает значение по ключу, а если ключа нет — сначала записывает умолчание: группировка гостей по городам без if-проверок, счётчики и подводный камень с вычислением аргумента.
Редакция Питоники
Метод get из урока 4 умеет возвращать умолчание, но не записывает его в словарь, а оператор in из урока 7 только отвечает «есть или нет». Для группировки нужен третий жест: при первом упоминании ключа создать пустой список, а дальше просто дополнять его. Это делает метод setdefault — буквально «установи по умолчанию».
Группировка: гости по городам
guests = [
('Казань', 'Анна'),
('Москва', 'Борис'),
('Казань', 'Вера'),
('Москва', 'Лев'),
]
trips = {}
for city, name in guests:
trips.setdefault(city, []).append(name)
print(trips)
{'Казань': ['Анна', 'Вера'], 'Москва': ['Борис', 'Лев']}Одна строка внутри цикла делает всю работу. setdefault смотрит на ключ: если города ещё нет — записывает под ним пустой список и возвращает его, и append тут же кладёт имя гостя. Если город уже встречался — возвращается существующий список. setdefault делает ровно одну вещь: при первом обращении создаёт умолчание, а дальше просто возвращает то, что уже лежит в словаре.
Что возвращает setdefault
d = {'чай': 2}
print(d.setdefault('чай', 0))
print(d.setdefault('кофе', 1))
print(d)
2
1
{'чай': 2, 'кофе': 1}Для ключа 'чай' умолчание 0 проигнорировано: вернулось настоящее значение 2, и словарь не изменился. Для 'кофе' ключа не было — умолчание 1 записалось в словарь и вернулось. Перезаписать существующее значение setdefault не может: оно всегда побеждает умолчание. Запомните это — многие ждут от метода обратного.
Сравнение с if-подходом
guests = [
('Казань', 'Анна'),
('Москва', 'Борис'),
('Казань', 'Вера'),
('Москва', 'Лев'),
]
trips = {}
for city, name in guests:
if city not in trips:
trips[city] = []
trips[city].append(name)
print(trips)
{'Казань': ['Анна', 'Вера'], 'Москва': ['Борис', 'Лев']}Три строки вместо одной — и везде одна церемония: проверить, создать, дописать. Явный if незаменим, когда при отсутствии ключа надо сделать что-то необычное — напечатать предупреждение, подставить заглушку. Когда же умолчание стандартное, setdefault короче и не оставляет места опечатке в двух местах. А излишен он там, где ключ только читается: для чтения без вставок верните get из урока 4.
setdefault, get и in: кто что делает
Три инструмента чтения, которые легко перепутать, потому что все три вызываются на словаре и все три что-то возвращают. Разница — ровно в одном: меняют они словарь или нет.
| Вызов | Ключа нет | Ключ есть | Меняет словарь |
|---|---|---|---|
| d.get(k, 0) | вернёт 0 | вернёт значение | нет |
| k in d | False | True | нет |
| d.setdefault(k, 0) | запишет 0 и вернёт его | вернёт значение | да, при промахе |
Запомните эту таблицу — она снимает большинство вопросов на собеседованиях. get и in — только чтение, словарь после них остаётся прежним. setdefault — единственный из трёх, кто умеет вставлять: при промахе он записывает умолчание и возвращает его. Если вставка вам не нужна, а нужен только результат, — берите get.
d = {'чай': 2}
print(d.get('чай', 0))
print('чай' in d)
print(d.setdefault('чай', 0))
print(d.setdefault('кофе', 1))
print(d)
2
True
2
1
{'чай': 2, 'кофе': 1}Первые три строки — про существующий ключ 'чай': все три дают 2 или True, и словарь не меняется. Четвёртая — про отсутствующий 'кофе': setdefault записал единицу и вернул её. В выводе видно, что пара 'кофе' теперь есть — в отличие от get, который бы ничего не добавил.
Подводный камень: аргумент считается всегда
Вот ловушка, на которой попадаются даже опытные. Второй аргумент setdefault — обычное выражение, и Python вычисляет его при каждом вызове, независимо от того, есть ключ или нет. Формально это правильно: значение нужно, чтобы вернуть его при промахе. Практически это значит, что дорогое вычисление в аргументе будет выполняться впустую на каждой итерации цикла.
d = {'чай': 2}
def make_default():
print('создаём заготовку')
return []
d.setdefault('чай', make_default())
print(d)
создаём заготовку
{'чай': 2}Ключ 'чай' на месте, setdefault вернул 2 — но функция make_default всё равно отработала и напечатала своё сообщение. Заготовка создана и тут же выброшена. С пустым списком это ничтожно, а с чем-то вроде datetime.now() или тяжёлого запроса — уже заметная потеря. Правило простое: во второй аргумент кладите только то, что стоит создавать.
Сценарий: счётчик слов одной строкой
Классическое применение setdefault — счётчик, который раньше писали тремя строками. Теперь умолчание отдаёт ноль, к нему прибавляется единица, и результат пишется обратно в тот же ключ. Запись получается плотной, но её смысл стоит разобрать.
counts = {}
words = ['чай', 'кофе', 'чай', 'чай']
for w in words:
counts[w] = counts.setdefault(w, 0) + 1
print(counts)
{'чай': 3, 'кофе': 1}Читается это так: взять значение по ключу w (ноль, если ключа нет), прибавить единицу, записать обратно. Никакой отдельной проверки if не нужно — умолчание делает её сама. Но честно скажем: такая запись читается хуже, чем counts.get(w, 0) + 1, и уступает Counter из урока 11 и defaultdict(int) из урока 12. Её стоит знать, но писать новый код на ней я бы не стал.
counts = {}
words = ['чай', 'кофе', 'чай', 'чай']
for w in words:
counts[w] = counts.get(w, 0) + 1
print(counts)
{'чай': 3, 'кофе': 1}Результат тот же, и разница только в одном: get ничего не вставляет. Пока ключ не существует, словарь пуст — а в варианте с setdefault под каждым встреченным ключом сразу появляется запись с нулём. Для счётчика это незаметно, но в других задачах разница принципиальна: get оставляет словарь чистым, setdefault — заполняет его.
Альтернатива: группировка по отсортированному списку
Ручная группировка через setdefault работает всегда, но требует прохода по всем записям и памяти под словарь, где у каждой группы лежит свой список. Если записи уже идут отсортированными по ключу — а выгрузки из баз часто именно такие, — можно обойтись вообще без словаря: модуль itertools из стандартной библиотеки умеет разбирать отсортированный список на группы.
from itertools import groupby
rows = [
('Казань', 'Анна'),
('Казань', 'Вера'),
('Москва', 'Борис'),
('Москва', 'Лев'),
]
for city, group in groupby(rows, key=lambda pair: pair[0]):
print(city, [name for _, name in group])
Казань ['Анна', 'Вера'] Москва ['Борис', 'Лев']
Код выглядит короче и не тратит память на словарь, но платит за это требованием: записи должны идти подряд и быть отсортированы. Перемешали список — получите не две группы, а четыре обрывка по одной записи, и никакой ошибки не будет. В проектах мы держим setdefault основным приёмом, а groupby берём только там, где сортировку гарантирует сам источник данных.
Что дальше
Метод setdefault разобран: он возвращает значение, при промахе создаёт умолчание и никогда не перезаписывает существующее. Дальше — два инструмента, которые делают то же самое, но удобнее: Counter в уроке 11 считает повторы сам, а defaultdict в уроке 12 создаёт значение по заданному типу без всяких цепочек.
Сначала предскажи ответ в голове — это главный навык программиста.
d = {'чай': 2}
print(d.setdefault('чай', 0))
print(d.setdefault('кофе', 1))
e = {}
print(e.setdefault('a', []).append(1))
print(e)
groups = {}
groups.setdefault('а', []).append('аист')
groups.setdefault('а', []).append('арбуз')
print(groups)
1. Что делает d.setdefault(ключ, умолчание), если ключа в словаре нет?
2. Что делает setdefault, если ключ уже есть?
3. Дано d = {'чай': 2}. Что вернёт d.setdefault('чай', 0)?
4. Почему группировку пишут цепочкой d.setdefault(k, []).append(x), а не через присваивание результата?
5. Когда setdefault излишен?
6. Какой из трёх вызовов умеет менять словарь?
7. Что произойдёт, если во второй аргумент setdefault передать вызов дорогой функции?
8. Как получить счётчик слов одной строкой в цикле, не проверяя наличие ключа?
Дан список words = ['аист', 'берёза', 'арбуз', 'банан']. Собери словарь groups: ключ — первая буква слова, значение — список слов на эту букву. Используй setdefault.
Что делает setdefault в Python?
Возвращает значение по ключу, а если ключа нет — записывает умолчание в словарь и возвращает его: d.setdefault('кофе', 1) при отсутствии 'кофе' создаст пару со значением 1. Если ключ есть, метод просто вернёт то, что лежит, без перезаписи.
Урок 4: метод getЧем setdefault отличается от get?
Только судьбой умолчания при промахе: get возвращает его, не трогая словарь, setdefault — вставляет в словарь. Для чистого чтения берите get, для группировки и ленивого заполнения — setdefault.
Как сгруппировать значения по ключу в словаре?
Цепочкой setdefault и append в цикле: trips.setdefault(city, []).append(name). При первом городе создастся пустой список, дальше имена будут дописываться в него.
Когда setdefault лучше явного if, а когда нет?
setdefault выигрывает, когда умолчание стандартное и логика одна — создать и дописать. Явный if not in нужен, когда при отсутствии ключа надо сделать что-то нестандартное: напечатать сообщение, подставить сложную заглушку.
Урок 12: defaultdictЧем setdefault отличается от get?
Только судьбой умолчания при промахе. get возвращает его, не трогая словарь; setdefault сначала записывает умолчание в словарь, потом возвращает. Для чистого чтения берите get, для группировки — setdefault или defaultdict.
Почему во второй аргумент setdefault не кладут дорогой вызов?
Потому что это обычное выражение, и Python вычисляет его при каждом вызове — даже если ключ уже есть и умолчание будет выброшено. Для пустого списка это незаметно, для datetime.now() или сетевого запроса — уже потеря. Кладите туда только то, что дёшево создать.
Как одной строкой завести счётчик слов в цикле?
counts[w] = counts.setdefault(w, 0) + 1. setdefault отдаёт ноль для невиданного слова, поэтому проверка не нужна. Альтернативы проще: Counter(words) из collections или defaultdict(int) с записью counts[w] += 1.
Урок 11: CounterПонравился урок? Сошлитесь на него
«setdefault делает ровно одну вещь: при первом обращении создаёт умолчание, а дальше просто возвращает то, что уже лежит в словаре.»
Скопируйте готовую ссылку в формате HTML, Markdown или чистый адрес и вставьте в статью на Habr, VC, Telegram-канал или свой блог — так о проекте узнают новые читатели.
Что дальше
Осталось в разделе: 20 из 20. Прогресс хранится в вашем браузере.
Что читать дальше
Словари · Урок 9
Вложенные словари: словарь внутри словаря
Каталог товаров, где у каждого товара свой словарь с ценой и остатком: доступ по цепочке ключей, запись во вложенные поля, перебор и разбор того, почему два уровня вложенности — это предел.
Словари · Урок 11
Counter: словарь, который считает сам
Counter из collections — словарь, который считает сам: слова строки, буквы слова, повторения в списке. most_common отдаёт самых частых, арифметика счётчиков сравнивает две выборки.
Словари · Урок 12
defaultdict: словарь с заводским умолчанием
defaultdict сам создаёт значение при первом обращении: списки для группировки и нули для счётчиков — без setdefault и проверок. Плюс factory, рецепты по типам и грабли с лишними ключами.
Похожие уроки по темам
Подобраны автоматически по пересечению тем и ключевых слов.
json · Урок 8
Путь к значению: достаём данные из глубины
Цепочки data["user"]["name"] и data["items"][0]["price"], KeyError у отсутствующих полей и get с умолчанием — спуск по типичному ответу API без падений.
python достать значение из jsondict get python умолчание
Исключения · Урок 11
KeyError в Python: ошибки словарей
Словарь не нашёл ключ — и честно сказал об этим KeyError. Разбираем объект ошибки, читаем из него ключ и выбираем между get, in и честным исключением.
keyerror словарь pythonpython словарь нет ключа
Словари · Урок 1
Словарь в Python: ключ и значение
Словарь — структура «ключ — значение»: достаём номер телефона по имени из телефонной книги, сравниваем dict со списком на живых данных и заглядываем под капот — как Python находит значение по ключу.
словарь pythonсловарь в питоне