Перейти к содержанию

412 уроков, 25 библиотек и челлендж «Что выведет код?» — бесплатно, код прямо в браузере

Начать обучение
Урок 4 из 20 Начальный 15 мин 100 XP

get: безопасное чтение из словаря

Метод get читает словарь без KeyError: без ключа вернёт None, со вторым аргументом — запасное значение. Сравниваем с квадратными скобками, разбираем арифметику с None и молчание get.

Редакция Питоники

В прошлом уроке строгий доступ d[ключ] падал с KeyError, стоило ключу отсутствовать. Иногда это ровно то, что нужно. Но чаще отсутствие ключа — нормальная ситуация: товара может не быть на складе, слова — в переводе, пользователя — в базе. Для таких случаев у словаря есть метод get — чтение без сюрпризов.

get: чтение без падений

Метод вызывается через точку, ключ передаётся в скобках. Для существующего ключа get ведёт себя ровно как квадратные скобки — вся разница видна на отсутствующем:

get при наличии и отсутствии ключа
stock = {'молоко': 12, 'хлеб': 5}

print(stock.get('молоко'))
print(stock.get('кефир'))
Вывод
12
None

Второй аргумент: значение по умолчанию

Для 'кефир' метод вернул None — специальное значение «здесь ничего нет»: программа не упала, а честно ответила. Но слово «None» в отчётах выглядит странно, а в арифметике и вовсе опасно. Поэтому у get есть второй аргумент — запасное значение, которое вернётся, если ключа нет:

умолчание вместо None
stock = {'молоко': 12, 'хлеб': 5}

print(stock.get('кефир', 0))

total = stock.get('кефир', 0) + stock.get('хлеб', 0)
print(total)
Вывод
0
5

Умолчание работает только при отсутствии ключа: если 'хлеб' на месте, вернётся его 5, а не 0. Здесь и главная польза: складывать запасы можно одной строкой, не проверяя заранее, какие позиции есть, — отсутствующие просто посчитаются нулями.

get не меняет словарь
stock = {'молоко': 12}

found = stock.get('кефир', 0)
print(found)
print(stock)
Вывод
0
{'молоко': 12}
d[ключ]d.get(ключ, 0)
Ключ естьзначениезначение
Ключа нетKeyError — программа падаетумолчание 0
Когда братьключ обязан существоватьотсутствие — норма

Сценарий: отчёт по складу за неделю

Соберём задачу, в которой get проявляет себя по-разному в зависимости от того, как написан код. Есть словарь остатков: {'молоко': 12, 'хлеб': 5, 'сыр': 8}. Нужно посчитать, сколько всего единиц товара лежит на складе, и вывести остаток по каждой позиции, включая ту, которой на складе нет.

отчёт по складу
stock = {'молоко': 12, 'хлеб': 5, 'сыр': 8}

total = 0
for item in stock.values():
    total += item
print('всего позиций:', len(stock))
print('сумма:', total)
Вывод
всего позиций: 3
сумма: 25

Тут get вообще не понадобился: мы итерировались по значениям, а не искали ключ. Ошибка в этом коде другая и куда более коварная — если бы в словаре завелась позиция с количеством None, сложение сразу упало бы с TypeError. Значения словарей стоит проверять на тип так же внимательно, как ключи.

выводим позицию, которой может не быть
stock = {'молоко': 12}

for name in ['молоко', 'кефир']:
    amount = stock.get(name, 0)
    print(name, '-', amount)
Вывод
молоко - 12
кефир - 0

Здесь get спасение: без него цикл упал бы на втором шаге. И заметьте — словарь не изменился, лишней пары 'кефир' не появилось. Если бы в коде вместо get стояло stock[name] = 0, в словаре появилась бы лишняя позиция с нулём, и len(stock) вырос бы на единицу. Иногда это безобидно, иногда — нет: в отчёте о товарах, которых нет в наличии, вдруг начинают числиться нули.

get не меняет словарь
stock = {'молоко': 12, 'хлеб': 5}
stock.get('кефир', 0)
stock.get('сыр', 0)
print(stock)
print(len(stock))
Вывод
{'молоко': 12, 'хлеб': 5}
2

Ловушка молчания: опечатка, которую никто не заметит

Самая неприятная особенность get — не в том, что он возвращает None, а в том, что он не спрашивает. Ключ написан с опечаткой, программа его не находит, get вежливо отдаёт 0, и расчёт продолжается. Ошибки нет, предупреждения нет, в логах пусто. Сумма за месяц получается меньше на одну позицию, и понять, откуда дыра, можно только пересчитывая всё вручную.

Есть и вторая сторона молчания: get возвращает None и тогда, когда ключ есть, но значение действительно None. Для словаря с необязательными полями это нормально, но сравнивать нужно с is None, а не с == None. Разница на практике небольшая, а вот ошибка if not d.get('поле') — уже настоящая: она считает поле отсутствующим и в случае с значением 0, и в случае с пустой строкой, хотя поле есть.

ноль — это не пусто
order = {'id': 7, 'discount': 0}

print('discount в заказе:', 'discount' in order)
print('значение через not:', not order.get('discount'))
print('значение через is None:', order.get('discount') is None)
Вывод
discount в заказе: True
значение через not: True
значение через is None: False

Ключ в заказе есть, значение равно нулю — а not всё равно говорит «правда». Значит, проверка «есть ли скидка» через not order.get('discount') отбросит заказ с нулевой скидкой так же, как заказ без скидки. Скидка 0 процентов и отсутствие скидки — разные вещи, и код должен это различать.

get и setdefault: похожи, но не одно и то же

У словаря есть ещё один метод, который снаружи выглядит как get, но работает иначе: setdefault. Он тоже принимает ключ и запасное значение, но если ключа нет — не просто вернёт умолчание, а добавит пару в словарь. Один вызов — и в словаре появилась новая запись.

get не добавляет, setdefault добавляет
stock = {'молоко': 12}

print(stock.get('кефир', 0))
print(stock)

print(stock.setdefault('кефир', 0))
print(stock)
Вывод
0
{'молоко': 12}
0
{'молоко': 12, 'кефир': 0}

Разница видна во второй строке вывода: после get в словаре по-прежнему одна пара, после setdefault — две. Метод удобен там, где пустой слот нужно действительно заполнить: накопление списка по ключу, счётчик, первое появление даты. Но в коде, который просто читает, setdefault становится ловушкой — он меняет словарь там, где этого никто не ждал.

В одном сервисе мы заменили d.get(key, []) на d.setdefault(key, []) — и через неделю словарь распух в десять раз: каждый запрос к несуществующему ключу оставлял после себя пустой список. Данные не испортились, но память уходила, а в логах было тихо. Если нужен именно список под ключом, берите defaultdict(list) — про него урок 12.

Что дальше

Теперь умеете читать мягко и понимаете, чем за это платите. Дальше — изменение словаря: как добавить пару, как заменить значение и почему ключ всегда остаётся один. В уроке 5 разбираем присваивание по ключу и историю с дубликатами, а в уроке 6 — удаление: del, pop и popitem.

Что выведет код?

Сначала предскажи ответ в голове — это главный навык программиста.

stock = {'чай': 3}

print(stock.get('кофе'))
print(stock.get('кофе', 0))
d = {'a': 10}

print(d.get('a', 99))
print(d.get('b', 99))
stock = {'молоко': 12}

stock.get('кефир', 0)
print(stock)
Проверь себя
0 / 8

1. Что вернёт d.get('ключ'), если такого ключа в словаре нет?

2. Зачем get второй аргумент: d.get('кефир', 0)?

3. Ключ 'хлеб' есть в словаре. Что вернёт d.get('хлеб', 0), если под ним лежит 5?

4. В каком случае get навредит, а квадратные скобки спасут?

5. Что опасно делать с результатом d.get('кефир') без умолчания?

6. Словарь order = {'id': 7, 'discount': 0}. Что вернёт not order.get('discount')?

7. Чем опаснее get, чем строгий доступ d[ключ]?

8. Как отличить отсутствующий ключ от ключа со значением None?

Карточки терминов
Запомнено: 0 / 8
Практика

Дан словарь stock = {'молоко': 12}. Выведи количество кефира через get с умолчанием 0, затем выведи суммарный запас молока и кефира, посчитанный тоже через get.

practice.py
Вопросы и ответы по уроку

Что делает метод get у словаря в Python?

Читает значение по ключу без риска KeyError: если ключ есть, get вернёт его значение, как и квадратные скобки; если ключа нет — вернёт None или второй аргумент-умолчание: stock.get('кефир', 0).

Урок 3: KeyError и строгий доступ

Чем get лучше квадратных скобок?

Ниже — вопрос. Разница ровно одна: поведение при отсутствии ключа. Если отсутствие — норма (товара нет, слова нет), get удобнее: программа продолжит работу. Если ключ обязан быть, скобки честнее: KeyError сразу укажет на баг в данных.

Добавляет ли get отсутствующий ключ в словарь?

Нет. get только читает: вызов stock.get('кефир', 0) вернёт 0, но словарь останется прежним — пары 'кефир' в нём не появится. Добавить пару можно присваиванием: stock['кефир'] = 0.

Какое умолчание указывать в get?

То, которое корректно впишется в дальнейшую работу: 0 для числовых подсчётов, пустую строку для текста, пустой список для коллекций. Без умолчания вернётся None — его нельзя складывать с числами, поэтому в подсчётах всегда задавайте второй аргумент.

Почему get может привести к тихой ошибке?

Потому что метод не спрашивает, правильный ли ключ. Опечатка в имени ключа даёт умолчание вместо ошибки: программа продолжает считать, но итог получается неверным, и найти пропажу можно только вручную. Для ключей из конфигов, ручного ввода и внешних API надёжнее строгий доступ или проверка in.

Урок 7: проверка ключа оператором in

Как отличить отсутствующий ключ от ключа со значением None?

Одного get мало: без умолчания он вернёт None в обоих случаях. Проверяйте наличие ключа оператором in — он смотрит именно на ключи, — или сравнивайте результат get с None через is None.

Урок 7: оператор in

Нужно ли ставить умолчание, если значение точно не None?

Да, если результат пойдёт в арифметику или в сравнение с числом. Умолчание 0 превращает потенциальный TypeError в предсказуемое число и делает намерение явным. В коде, где значение используется только как флаг присутствия, get без умолчания вполне уместен.

Понравился урок? Сошлитесь на него

«get ни на что не жалуется: опечатка stock.get('малоко', 0) вернёт 0, и сумма получится неверной — без единой ошибки в логах.»

Скопируйте готовую ссылку в формате HTML, Markdown или чистый адрес и вставьте в статью на Habr, VC, Telegram-канал или свой блог — так о проекте узнают новые читатели.

TelegramVK

Что дальше

Осталось в разделе: 20 из 20. Прогресс хранится в вашем браузере.

Похожие уроки по темам

Подобраны автоматически по пересечению тем и ключевых слов.