except или if в Python: граница применимости проверки и исключения
Отсутствие ключа: проверить if-ом или поймать KeyError? Разбираем честную границу: обычные варианты — if, редкие сбои — except, и случай, где проверка проигрывает исключению всухую.
Редакция Питоники
Урок 14 свёл два стиля в формулу: EAFP — сначала действие, потом ловля; LBYL — сначала проверка, потом действие. Но формула не отвечает на главный практический вопрос: где проходит граница? Отсутствие ключа в словаре — проверять if-ом или ловить KeyError? Битое число — прогонять через isdigit или верить int с ValueError? Урок разбирает задачу за задачей и выводит правило, которое работает без оговорок.
Одна задача — два стиля
Настройка может отсутствовать — тогда берём умолчание. Вот эта задача в обеих формах, и результаты совпадают:
config = {'host': 'localhost', 'port': '8080'}
# стиль if: проверяем до действия
if 'port' in config:
port = int(config['port'])
else:
port = 80
print('if-стиль:', port)
# стиль except: действуем и ловим
try:
port = int(config['port'])
except KeyError:
port = 80
print('except-стиль:', port)
if-стиль: 8080 except-стиль: 8080
Обе ветки напечатали одно и то же — техники равносильны. Выбор делается не по технике, а по смыслу: считается ли отсутствие порта нормой или сбоем. Если конфиг без порта — обычное дело (умолчание прописано в документации), то if читается честнее: отсутствие — одна из ожидаемых веток программы. Но в этом конкретном случае у Python есть и третий, самый короткий вариант — get из урока про KeyError:
config = {'host': 'localhost'}
port = int(config.get('port', 80))
print('порт:', port)
print('host:', config.get('host', 'не задан'))
порт: 80 host: localhost
Когда отсутствие ключа обрабатывается подстановкой умолчания, отдельное ветвление и исключение одинаково избыточны — get выражает это одной строкой. Запомните порядок предпочтений для словарей: get — если есть умолчание; if in — если отсутствие требует разных действий; try/except KeyError — если отсутствие — редкий сбой, а не вариант.
Где проверка проигрывает: разбор числа
Классическая попытка проверить строку перед int — метод isdigit. Он отвечает «только цифры?» — и кажется идеальным стражем. Сравним его с честным разбором через int и ValueError из урока 12 на наборе кандидатов:
candidates = ['42', '-7', ' 5 ', '3.14', 'семь']
print('проверка isdigit:')
for text in candidates:
if text.isdigit():
print(' ', repr(text), '->', int(text))
else:
print(' ', repr(text), '-> отклонено')
print('разбор int-ом:')
for text in candidates:
try:
print(' ', repr(text), '->', int(text))
except ValueError:
print(' ', repr(text), '-> не число')
проверка isdigit: '42' -> 42 '-7' -> отклонено ' 5 ' -> отклонено '3.14' -> отклонено 'семь' -> отклонено разбор int-ом: '42' -> 42 '-7' -> -7 ' 5 ' -> 5 '3.14' -> не число 'семь' -> не число
Страж отсеял всё, что не чистые цифры, — включая числа, которые int разбирает прекрасно: минус семь и пятёрку с пробелами по краям. Данные потеряны не из-за ошибки, а из-за неточной проверки. Чтобы if-вариант догнал int, пришлось бы написать text.lstrip('-').isdigit() — и всё равно что-то упустить. Проверка сложнее действия — значит, действие с обработкой честнее.
# LBYL-цепочка: проверки повторяют действие
if text.isdigit() or (text.startswith('-') and text[1:].isdigit()):
number = int(text)
else:
number = None
if для обычных веток
Обратная сторона: если оба исхода частые и оба — норма, ветвление через исключение странно. Счётчик голосов с двумя типами ответов: половина «да», половина «нет» — оба случая встречаются в каждой итерации:
votes = ['да', 'нет', 'да', 'нет', 'да']
counts = {}
for v in votes:
if v in counts:
counts[v] += 1
else:
counts[v] = 1
print(counts)
{'да': 3, 'нет': 2}Писать здесь try: counts[v] += 1 except KeyError: counts[v] = 1 можно — будет работать. Но KeyError здесь ловился бы в каждой второй итерации: исключение перестало быть сигналом о сбое и стало обычной веткой логики. Исключение дорого именно в момент возникновения: Python строит объект, раскручивает стек. Частые сбои — довод в пользу if.
Спорный случай: пустая строка в списке слов
Есть случаи, где оба инструмента работают, и выбор — вопрос вкуса. Список слов, среди которых затесалась пустая:
words = ['кот', 'собака', '', 'ёж']
for word in words:
try:
print('первая буква:', word[0])
except IndexError:
print('пустая строка — букв нет')
первая буква: к первая буква: с пустая строка — букв нет первая буква: ё
Пустая строка — сбой или вариант? Если слова приходят из формы ввода и пустое поле — нормальное событие, честнее if not word: из уроков про строки: условие короткое и говорит само за себя. Если пустая строка — аномалия данных, которую вы редко ждёте, except IndexError с обработкой тоже оправдан. Критерий один и тот же: частота события и его статус в глазах программы.
for word in words:
if word:
print('первая буква:', word[0])
else:
print('пустая строка - букв нет')
| Ситуация | Инструмент | Почему |
|---|---|---|
| У ключа словаря есть умолчание | dict.get | Одна строка, без ветвлений |
| Отсутствие ключа — обычный вариант | if in / if not | Частый случай: исключение дорого |
| Отсутствие ключа — редкий сбой | try / except KeyError | Сигнал о поломке данных |
| Разбор числа с минусами и пробелами | int + except ValueError | Проверка сложнее действия |
| Файл мог исчезнуть между проверкой и открытием | try / except FileNotFoundError | Между if и open нет гарантий |
| Опечатка в коде | Никаких ловушек | Пусть падает и показывает строку |
Строка про файл — самый коварный случай: урок 13 о файлах показывал проверку os.path.exists, но между проверкой и открытием файл могут удалить — проверка отвечает за прошлое, исключение ловит настоящее. Именно поэтому для внешнего мира try надёжнее любого if.
import os
if os.path.exists('report.csv'):
f = open('report.csv') # файл могли удалить между двумя строками
Правило одной фразы
Если вариант обычный — if; если вариант — сбой — except. К нему два уточнения: когда проверка сложнее действия, выбирайте действие с исключением; когда сбои частые, исключение замените проверкой. Проверки и исключения при этом не конкуренты, а разные этажи одной защиты: if отсеивает ожидаемые варианты быстро и без шума, except обрабатывает то, что предвидеть проверкой невозможно, а assert из предыдущего урока следит, чтобы собственный код не соврал о себе. Осталось собрать всё в одну программу — финальный проект раздела: разбор файла с грязными данными, где try внутри цикла, счётчики и журнал работают как одна машина.
Сначала предскажи ответ в голове — это главный навык программиста.
config = {}
try:
port = int(config['port'])
except KeyError:
port = 80
print(port)
print('-7'.isdigit())
try:
print(int('-7'))
except ValueError:
print('отклонено')
counts = {'да': 1}
if 'нет' in counts:
counts['нет'] += 1
else:
counts['нет'] = 1
print(counts)
1. Отсутствие ключа в словаре — обычный вариант работы. Что выбрать?
2. Почему isdigit перед int — плохой страж?
3. Когда исключение проигрывает проверке по цене?
4. Почему для файла надёжнее try, а не os.path.exists?
5. Пустая строка в списке слов — норма для программы. Что честнее?
6. if-проверка не спасает от чего?
7. Правило одной фразы из урока звучит так:
Дан словарь stock с остатками: {'ручка': 12, 'тетрадь': 0}. Напишите функцию price_of(item), которая возвращает остаток товара, а при отсутствии — строку нет в наличии. Решите задачу через get с умолчанием и напечатайте результат для 'ручка' и для 'ластик'.
Что лучше в Python: if-проверка или try-except?
Зависит от статуса события: частый, ожидаемый вариант — if-проверка (или dict.get для умолчаний), редкий сбой данных — try-except. Исключение дорого в момент срабатывания, поэтому половина проходов цикла, падающих в except, — знак, что нужна проверка.
Урок 14: EAFP против LBYLПочему isdigit не подходит для проверки перед int?
isdigit возвращает True только для строк из цифр, а int разбирает и минусы, и пробелы по краям: '-7' и ' 5 ' отклоняются зря, данные теряются. Надёжнее вызывать int и ловить ValueError — он точно перечисляет, что разобрать не удалось.
Урок 12: ValueErrorКак проверить, есть ли ключ в словаре, и не поймать KeyError?
Три инструмента: dict.get(key, default) — если есть умолчание; if key in dict — если отсутствию нужны свои действия; try-except KeyError — если отсутствие редкий сбой. Для частых случаев первые два дешевле исключения.
Урок 11: KeyErrorПочему проверка файла os.path.exists ненадёжна?
Проверка отвечает за момент, когда её выполнили: между exists и open файл может удалить другой процесс. try с except FileNotFoundError проверяет настоящее состояние мира — для внешних ресурсов это единственная честная защита.
Урок 13: FileNotFoundErrorМожет ли if заменить обработку багов в коде?
Нет: if проверяет данные, а NameError от опечатки или TypeError от неверного типа — свойства кода. Они пройдут мимо любой проверки значений. За кодом следят assert-инварианты и тесты, за данные — try-except.
Урок 18: assertПонравился урок? Сошлитесь на него
«Проверка сложнее действия — значит, действие с обработкой честнее.»
Скопируйте готовую ссылку в формате HTML, Markdown или чистый адрес и вставьте в статью на Habr, VC, Telegram-канал или свой блог — так о проекте узнают новые читатели.
Что дальше
Осталось в разделе: 20 из 20. Прогресс хранится в вашем браузере.
Что читать дальше
Исключения · Урок 14
EAFP против LBYL в Python: два стиля проверки
Look Before You Leap — смотри, прежде чем прыгнуть; Easier to Ask Forgiveness than Permission — проще извиниться, чем спросить. Одна задача, два стиля, один вердикт.
Исключения · Урок 12
ValueError в Python: разбор строк в числа
int('восемнадцать') — тип подходящий, а значение не помещается. Разбираем сообщение invalid literal, границы int и float и честную проверку isdigit.
Исключения · Урок 20
Проект: надёжный разбор файла с грязными данными в Python
Итоговый проект: файл с мусором — пустые строки, слова вместо чисел, пробелы — не роняет отчёт, а попадает в журнал. Три куска: построчный разбор, счётчики с первыми ошибками и функция-парсер со сверкой.
Похожие уроки по темам
Подобраны автоматически по пересечению тем и ключевых слов.
Строки · Урок 11
Проверки строки: isdigit, isalpha и валидация ввода
Методы-вопросы: строка состоит только из цифр? только из букв? всё в нижнем регистре? Учимся валидировать ввод пользователя одним вызовом.
python isdigitisdigit python
re · Урок 9
Опережающие и ретроспективные проверки: lookahead и lookbehind
Проверки lookahead и lookbehind добавляют условие «только если рядом есть такой-то текст», не включая его в совпадение: пароль с тремя требованиями — одной регуляркой, разряды 1 000 000 — одной заменой.
опережающая проверка regexретроспективная проверка (?<=
Исключения · Урок 8
Повторное возбуждение: голый raise в except
Исключение поймано, но проблема не решена: голый raise внутри except передаёт её наверх, не теряя стек. Учимся честному пробросу и ловим RuntimeError от raise вне except.
повторное возбуждение исключения pythonraise в except python