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

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

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

Доступ по ключу и KeyError

Что происходит при доступе по несуществующему ключу: настоящий traceback, построчный разбор KeyError, отлов через try/except и случаи, когда падение — правильное поведение.

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

Доступ по ключу — самая частая операция над словарём. Пока ключ на месте, всё скучно и хорошо: запись d[ключ] — это выражение, и его результат можно сразу печатать, складывать, делить. Настоящий интерес начинается, когда ключа нет: Python не угадывает и не подставляет «что-нибудь» — он останавливает программу и объясняет, чего ему не хватило. Разберём это сообщение по косточкам: KeyError — одна из самых частых ошибок начинающего, и одна из самых честных.

Чтение по ключу: обычная операция

успешные доступы
marks = {'алгебра': 5, 'литература': 4, 'физика': 5}

print(marks['литература'])
print(marks['алгебра'] + marks['физика'])
Вывод
4
10
значение в выражении
marks = {'алгебра': 5, 'литература': 4}

average = (marks['алгебра'] + marks['литература']) / 2
print(average)
Вывод
4.5

Ключа нет: KeyError

Теперь обратимся к ключу, которого в словаре нет. Программа падает — вот полный текст, который печатает интерпретатор Python:

обращение к несуществующему ключу
marks = {'алгебра': 5, 'литература': 4}

print(marks['химия'])
Вывод
Traceback (most recent call last):
  File "example.py", line 3, in <module>
    print(marks['химия'])
          ~~~~~^^^^^^^^^
KeyError: 'химия'
Блок не запускается в браузерной песочнице: этот код падает. Путь к файлу и точный вид указателя зависят от того, как запущен скрипт, — суть в последних двух строках.

Читаем снизу вверх — так читают любую ошибку Python. Последняя строка — сама ошибка: KeyError: 'химия', тип и ключ, который не нашёлся. Выше — где это случилось: файл example.py, строка 3, и сама строка кода с указателем под выражением. Словарь честно сообщает, чего именно в нём нет: KeyError всегда называет ключ, который не нашёлся. Безопасное чтение без падений появляется в уроке про get, а предварительная проверка ключа — в уроке про in.

Когда строгий доступ оправдан

СитуацияЧто выбрать
Ключ обязан быть на месте, его отсутствие — баг в данныхd[ключ] — пусть упадёт громко и сразу
Ключ может отсутствовать, и это нормаd.get(ключ, умолчание) — урок про get
Сначала решить, есть ли ключ вообщеключ in d — урок про in

Падение — не всегда зло. Если ключ обязан быть на месте — настройка, обязательное поле записи, — лучше упасть сразу с внятной ошибкой, чем продолжить работу с тихим пустым значением и получить странные числа через час. Строгий доступ — это бесплатная проверка данных: словарь сам жалуется, если в нём не хватает пары. А для случаев, когда отсутствие — норма, в Python есть мягкие способы, и о них — следующие уроки раздела.

Как перехватить KeyError

Иногда падение нужно поймать, а не просто избежать. Например, программа читает оценки из внешнего файла и хочет для отсутствующего предмета поставить ноль, а не ругаться на весь файл. Для этого есть try/except: внутри try стоит опасный доступ, а except KeyError ловит именно эту ошибку и подставляет замену.

ловим KeyError и подставляем ноль
marks = {'алгебра': 5, 'литература': 4}

try:
    print(marks['химия'])
except KeyError as e:
    print('не нашли оценку:', e)
Вывод
не нашли оценку: 'химия'

Конструкция except KeyError as e кладёт саму ошибку в переменную e. Внутри неё лежит недостающий ключ — ровно то, что нужно для понятного сообщения: Python сам в квадратных скобках пишет 'химия'. Обратите внимание на кавычки в выводе: это не часть ключа, а способ показать, что перед нами строка.

в ошибке лежит сам ключ
marks = {'алгебра': 5}

try:
    print(marks['физика'])
except KeyError as e:
    print('Ключ:', e)
    print('Тип:', type(e).__name__)
Вывод
Ключ: 'физика'
Тип: KeyError

Так из ошибки вытаскивается диагностика: имя ключа и тип исключения. Это не академическое упражнение — когда KeyError летит из чужой библиотеки, извлечённый ключ часто сразу показывает, где именно искать проблему.

Три уровня защиты: от явного падения до тихого

Один и тот же сценарий — «ключа может не оказаться» — можно закрыть тремя разными способами, и выбор между ними говорит о том, как вы понимаете свои данные. Первый: упасть. Второй: проверить заранее и решить, что делать. Третий: взять значение по умолчанию и продолжить.

три способа на одном примере
marks = {'алгебра': 5}
subject = 'химия'

# способ 1: упасть
try:
    value = marks[subject]
except KeyError:
    value = 0

print('через try:', value)

# способ 2: проверить заранее
if subject in marks:
    print('через in:', marks[subject])
else:
    print('через in: нет оценки за', subject)

# способ 3: взять умолчание
print('через get:', marks.get(subject, 0))
Вывод
через try: 0
через in: нет оценки за химия
через get: 0

Все три дают один и тот же ответ на печать, но ведут себя по-разному в нештатной ситуации. Первый способ честнее всех: он превращает KeyError в обычное значение и при этом оставляет в коде явное упоминание, что ключ мог не найтись. Второй даёт две разные ветки — когда ключ есть и когда нет. Третий — самый короткий, но и самый скрытный: читатель кода не видит, что здесь вообще возможен промах.

Мой выбор по умолчанию — второй способ, когда у ключа есть осмысленное «иначе», и третий, когда промах действительно равнозначен нулю. Первый способ я оставляю там, где отсутствие ключа — признак сломанных данных: пусть программа упадёт громко и сразу, чем через час посчитает неверный итог. Подробно про get — в уроке 4, про проверку in — в уроке 7.

ловля ошибки в цикле
scores = {'Анна': 90}

for name in ['Анна', 'Борис']:
    try:
        print(name, scores[name])
    except KeyError:
        print(name, 'нет в списке')
Вывод
Анна 90
Борис нет в списке

Здесь try стоит внутри цикла, и это важно: так одно отсутствие не срывает обработку остальных. Если бы try накрывал весь цикл, первая же ошибка вышла бы наружу и оставшиеся элементы не были бы обработаны. Мелочь, на которой спотыкаются при переносе кода из одного проекта в другой.

Откуда берётся KeyError на самом деле

В учебных примерах KeyError случается, когда код забыл проверить ключ. В рабочих программах почти всегда иначе: ключ приезжает извне. Ответ API, разобранный json, файл конфигурации, строка из CSV — за эти данные отвечает не ваш код, а тот, кто их прислал. В словаре из ответа сервера может не оказаться поля 'total', потому что на одной из машин его не отдают.

Мы как-то полчаса искали, почему отчёт по продажам падает на пятой строке файла. Первые четыре строки писала одна система, пятую — руками в таблице, и колонки со скидкой в ней не было. Ключа 'скидка' не оказалось у части записей, и каждое падение выглядело одинаково: KeyError: 'скидка'. В сообщении нет имени словаря, только сам ключ, — когда такое имя встречается сразу в нескольких словарях, приходится гадать, который из них неполный.

цепочка словарей и место падения
record = {'id': 17, 'client': {'name': 'Ирина'}}

print(record['client']['name'])

try:
    print(record['client']['email'])
except KeyError as e:
    print('нет поля:', e)
Вывод
Ирина
нет поля: 'email'

Вложенный словарь падает на той ступени, где не хватило ключа, а не на самом внешнем уровне. Поэтому в сообщении видно 'email' — это поле клиента, а не записи: если бы ключа не было уровнем выше, в ошибке появилось бы 'client'. Мелочь, которая экономит время: по имени отсутствующего ключа сразу понятно, на каком уровне копать.

Что дальше

Теперь вы умеете читать KeyError, ловить его и выбирать между тремя стратегиями. Дальше — мягкий вариант чтения: метод get, который возвращает значение по умолчанию вместо падения. В уроке 4 разберём, когда get спасает, а когда, наоборот, молчание опаснее падения. Ну а если хочется сначала просто проверить наличие ключа — это урок 7 про оператор in.

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

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

marks = {'алгебра': 5, 'химия': 4}

print(marks['химия'] + marks['алгебра'])
d = {'1': 'строка-ключ'}
print(d[1])
stock = {'молоко': 12}
print(stock['Молоко'])
Проверь себя
0 / 8

1. Что произойдёт при доступе d['ключ'], если такого ключа в словаре нет?

2. Какую строку traceback стоит читать первой?

3. Словарь {'алгебра': 5}. Что вернёт d['Алгебра'] с большой буквы?

4. Когда строгий доступ d[ключ] — правильный выбор?

5. Что показывает указатель ~~~^^^ под строкой кода в traceback?

6. Как в Python перехватить KeyError и узнать, какого ключа не хватило?

7. Чем плох пустой except: без указания типа ошибки?

8. Где стоит размещать try внутри цикла по словарю?

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

Собери словарь marks с оценками: алгебра — 5, химия — 4. Выведи оценку по химии, затем выведи средний балл — сумму двух оценок, делённую на 2.

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

Что значит KeyError в Python?

Это ошибка обращения к словарю по ключу, которого нет: d['химия'] при отсутствии ключа 'химия' останавливает программу. В тексте ошибки всегда назван недостающий ключ — по нему легко найти опечатку или пропущенную пару.

Урок 4: чтение через get без KeyError

Как исправить KeyError?

Сначала прочитать последнюю строку traceback — там назван ключ. Затем сверить его с настоящими ключами: чаще всего виноваты опечатка, регистр ('Молоко' вместо 'молоко') или лишний пробел. Если ключа может и не быть — это норма, берите get или проверку in.

Почему доступ по ключу падает, а не возвращает None?

Потому что строгий доступ — это проверка данных: если обязательный ключ отсутствует, лучше узнать об этом сразу и громко, чем час работать с тихим пустым значением. Для ситуаций, когда отсутствие ключа нормально, в Python есть отдельные инструменты — get и in.

KeyError возникает только у словарей?

Главный источник — словари, но ошибка возможна везде, где есть поиск по ключу: например, в json-данных после распаковки или в os.environ. Логика одна: запрошенного ключа в коллекции не оказалось.

Как обработать KeyError в программе?

Через try/except KeyError. Внутри try стоит доступ по ключу, а except ловит ошибку и подставляет замену. Конструкция except KeyError as e кладёт саму ошибку в e — там лежит недостающий ключ, его удобно показать пользователю в понятном сообщении.

Урок 4: безопасное чтение через get

Почему не стоит писать голый except без типа ошибки?

Пустой except перехватит вообще что угодно: опечатку в имени переменной, деление на ноль, KeyError. Программа продолжит работу с испорченными данными, а настоящая причина сбоя потеряется. Ловите конкретный тип и обязательно оставляйте в теле except сообщение или повторный raise.

Как выбрать между d[ключ], d.get(ключ) и проверкой in?

d[ключ] — когда отсутствие ключа означает сбой в данных и нужно упасть громко. Проверка in — когда по наличию ключа выбираются два разных действия. d.get(ключ, 0) — когда промах равнозначен значению по умолчанию, и код должен остаться коротким.

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

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

«Словарь честно сообщает, чего именно в нём нет: KeyError всегда называет ключ, который не нашёлся.»

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

TelegramVK

Что дальше

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

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

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