Доступ по ключу и 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 ловит именно эту ошибку и подставляет замену.
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['Молоко'])
1. Что произойдёт при доступе d['ключ'], если такого ключа в словаре нет?
2. Какую строку traceback стоит читать первой?
3. Словарь {'алгебра': 5}. Что вернёт d['Алгебра'] с большой буквы?
4. Когда строгий доступ d[ключ] — правильный выбор?
5. Что показывает указатель ~~~^^^ под строкой кода в traceback?
6. Как в Python перехватить KeyError и узнать, какого ключа не хватило?
7. Чем плох пустой except: без указания типа ошибки?
8. Где стоит размещать try внутри цикла по словарю?
Собери словарь marks с оценками: алгебра — 5, химия — 4. Выведи оценку по химии, затем выведи средний балл — сумму двух оценок, делённую на 2.
Что значит 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-канал или свой блог — так о проекте узнают новые читатели.
Что дальше
Осталось в разделе: 20 из 20. Прогресс хранится в вашем браузере.
Что читать дальше
Словари · Урок 2
Создание словаря: фигурные скобки и dict()
Три способа создать словарь: литерал в фигурных скобках, dict() из списка пар и пустой словарь под наполнение. Плюс разбор, какие ключи годятся и почему fromkeys с пустым списком — ловушка.
Словари · Урок 4
get: безопасное чтение из словаря
Метод get читает словарь без KeyError: без ключа вернёт None, со вторым аргументом — запасное значение. Сравниваем с квадратными скобками, разбираем арифметику с None и молчание get.
Словари · Урок 7
in: проверка ключа в словаре
Оператор in отвечает на вопрос «есть ли такой ключ»: проверка перед доступом вместо KeyError, not in, грабли с проверкой значений и причина скорости поиска.
Похожие уроки по темам
Подобраны автоматически по пересечению тем и ключевых слов.
Исключения · Урок 11
KeyError в Python: ошибки словарей
Словарь не нашёл ключ — и честно сказал об этим KeyError. Разбираем объект ошибки, читаем из него ключ и выбираем между get, in и честным исключением.
python keyerrorkeyerror python
Множества · Урок 10
frozenset: неизменяемое множество в Python
Множество, которое нельзя менять: frozenset создаётся один раз, поддерживает все проверки и операции чтения и годится туда, куда set не пускают — ключом в словарь и элементом другого множества.
frozenset ключ словаря
Словари · Урок 1
Словарь в Python: ключ и значение
Словарь — структура «ключ — значение»: достаём номер телефона по имени из телефонной книги, сравниваем dict со списком на живых данных и заглядываем под капот — как Python находит значение по ключу.
словарь pythonсловарь в питоне