Конструкция try/except перехватывает исключение времени выполнения, чтобы программа обработала сбой и продолжила работу. В try помещают операцию, которая может завершиться ошибкой; в except указывают конкретный тип исключения и реакцию: показать сообщение, вернуть запасное значение или повторить ввод. Примеры проверены на CPython 3.14.5.
finally объединяет обе ветки
try работает else, после ValueError — подходящий except. Обе ветки затем проходят через finally.Как работает try/except
Без перехвата первая же ошибка обрывает программу, и следующие строки не выполняются:
age = int("двадцать")
print("сюда не дойдёт")
Traceback (most recent call last):
File "main.py", line 1, in <module>
age = int("двадцать")
~~~^^^^^^^^^^^^
ValueError: invalid literal for int() with base 10: 'двадцать'
Трассировка читается снизу вверх: последняя строка называет тип ошибки и причину, выше стоит место, где это случилось. Тот же код внутри try не роняет программу:
try:
age = int("двадцать")
except ValueError as exc:
print(f"ValueError: {exc}")
ValueError: invalid literal for int() with base 10: 'двадцать'
Часть as exc кладёт сам объект исключения в переменную, и из него достаётся текст ошибки. Без as перехват тоже работает, просто сообщение недоступно.
Блоков except может быть несколько, каждый на свой тип:
data = {"хлеб": 60}
for key in ["хлеб", "масло"]:
try:
print(key, data[key])
except KeyError as exc:
print(f"нет ключа {exc}")
хлеб 60
нет ключа 'масло'
Обрати внимание: KeyError печатает ключ в кавычках, потому что показывает его представление, а не строку. Способы обойтись без перехвата вовсе, через get и in, разобраны в статье про словари.
Один блок ловит несколько типов, если перечислить их кортежем:
for value in ["10", "десять", None]:
try:
print(int(value))
except (ValueError, TypeError) as exc:
print(f"{type(exc).__name__}: {exc}")
10
ValueError: invalid literal for int() with base 10: 'десять'
TypeError: int() argument must be a string, a bytes-like object or a real number, not 'NoneType'
Строка «десять» даёт ValueError, а None даёт TypeError: тип не тот, до разбора содержимого дело не доходит.
Как поймать конкретную ошибку, а не всё подряд
Исключения выстроены в иерархию, и except ловит указанный класс вместе со всеми его потомками. Отсюда правило: узкие типы пишут выше широких, иначе широкий заберёт всё себе.
try:
int("нет")
except Exception:
print("поймал Exception")
except ValueError:
print("поймал ValueError")
поймал Exception
Второй блок недостижим: ValueError — потомок Exception, и до него очередь не доходит. Правильный порядок даёт другой ответ:
try:
int("нет")
except ValueError:
print("поймал ValueError")
except Exception:
print("поймал Exception")
поймал ValueError
Отдельная беда — голый except: без типа. Он ловит вообще всё, включая то, что ловить не надо:
import sys
try:
sys.exit(1)
except Exception:
print("Exception не поймал SystemExit")
except BaseException as exc:
print(f"BaseException поймал {type(exc).__name__}")
BaseException поймал SystemExit
SystemExit и KeyboardInterrupt наследуются от BaseException в обход Exception именно затем, чтобы обычный перехват их пропускал. Голый except: равен except BaseException: и ломает выход из программы вместе с прерыванием по Ctrl+C. Пиши except Exception:, если действительно нужен широкий перехват.
Ещё хуже перехват с пустым телом:
result = None
try:
result = 1 / 0
except Exception:
pass
print("result =", result)
result = None
Ошибка проглочена, следов не осталось, переменная тихо осталась пустой и уедет дальше по коду. Если ловишь ошибку, делай с ней что-то: логируй, подставляй запасное значение, поднимай своё исключение.
else и finally: что и когда выполняется
else выполняется, если блок try завершился без исключения и без return, break или continue. finally выполняется при выходе из конструкции независимо от выбранной ветки.
for value in ["10", "нет"]:
try:
number = int(value)
except ValueError:
print("не число")
else:
print("разобрал", number)
finally:
print("finally отработал")
разобрал 10
finally отработал
не число
finally отработал
Порядок зависит от исхода: после try выполняется подходящий except при исключении либо else при нормальном завершении; последним выполняется finally.
try:
print("1 try")
except ValueError:
print("except")
else:
print("2 else")
finally:
print("3 finally")
1 try
2 else
3 finally
Зачем нужен else, если код можно оставить в try. Затем, что try должен содержать только то, что мы готовы перехватить. Положишь в него ещё и обработку результата, и случайный ValueError из этой обработки будет пойман тем же except, который писался про разбор числа. Ошибка окажется замаскирована под чужую.
finally нужен для уборки: закрыть файл, вернуть соединение в пул, снять блокировку. Он отрабатывает и тогда, когда исключение уходит наружу непойманным:
def raises():
try:
raise ValueError("сломалось")
finally:
print("finally всё равно выполнился")
try:
raises()
except ValueError as exc:
print(f"наружу вышло: {exc}")
finally всё равно выполнился
наружу вышло: сломалось
На практике для уборки чаще берут не finally, а with: он делает то же самое короче и не даёт забыть закрытие. Как устроен этот протокол, разобрано в статье про контекстные менеджеры.
Почему return в finally побеждает
Самый частый вопрос про finally на собеседовании. Ответ: возврат из finally перекрывает возврат из try.
def with_finally_return():
try:
return "из try"
finally:
return "из finally"
print(with_finally_return())
main.py:5: SyntaxWarning: 'return' in a 'finally' block
return "из finally"
из finally
Функция вернула «из finally», хотя return в try уже отработал. Заодно Python 3.14 сам предупреждает об этой конструкции: SyntaxWarning появляется при компиляции, ещё до вызова функции. То же предупреждение выдаётся на break и continue, если они покидают блок finally, а не только вложенный в него цикл.
Опасность не в самом возврате, а в том, что finally точно так же перекрывает и исключение:
def swallow():
try:
raise ValueError("важная ошибка")
finally:
return "как будто всё хорошо"
print(swallow())
main.py:5: SyntaxWarning: 'return' in a 'finally' block
return "как будто всё хорошо"
как будто всё хорошо
Исключение с текстом «важная ошибка» исчезло бесследно: функция вернула строку, вызывающий код ничего не заподозрил. Кроме предупреждения при компиляции, никаких следов не осталось. Не пиши return, break и continue внутри finally.
Без возврата всё ведёт себя предсказуемо: finally отрабатывает и уступает дорогу:
def finally_without_return():
try:
return "из try"
finally:
print("finally отработал до возврата")
print(finally_without_return())
finally отработал до возврата
из try
Как поднять своё исключение
Оператор raise бросает исключение вручную. Свой класс наследуют от Exception:
class NotEnoughFunds(Exception):
pass
def withdraw(balance, amount):
if amount > balance:
raise NotEnoughFunds(f"не хватает {amount - balance}")
return balance - amount
try:
withdraw(1000, 1500)
except NotEnoughFunds as exc:
print(f"{type(exc).__name__}: {exc}")
NotEnoughFunds: не хватает 500
Свой тип лучше готового ValueError тем, что вызывающий код может поймать именно эту ситуацию, не задевая остальные. Тело класса при этом обычно пустое: всё нужное уже есть в Exception.
Когда одно исключение возникает из-за другого, их связывают через raise ... from:
try:
try:
int("нет")
except ValueError as exc:
raise RuntimeError("не удалось разобрать конфиг") from exc
except RuntimeError as exc:
print(f"{exc}")
print(f"причина: {type(exc.__cause__).__name__}: {exc.__cause__}")
не удалось разобрать конфиг
причина: ValueError: invalid literal for int() with base 10: 'нет'
В трассировке обе ошибки окажутся рядом с пометкой «The above exception was the direct cause of the following exception», а без from связь тоже сохранится, только пометка будет другой, «During handling of the above exception, another exception occurred».
Иногда исключение надо залогировать и пропустить дальше. Для этого raise пишут без аргументов, и он поднимает текущее исключение заново:
try:
try:
int("нет")
except ValueError:
print("залогировал")
raise
except ValueError as exc:
print(f"дошло наружу: {exc}")
залогировал
дошло наружу: invalid literal for int() with base 10: 'нет'
Что try/except поймать не может
Синтаксические ошибки и ошибки отступов. Они возникают при разборе файла, то есть до того, как выполнится хоть одна строка, включая сам try:
try:
if True
print("привет")
except SyntaxError:
print("поймал")
File "main.py", line 2
if True
^
SyntaxError: expected ':'
Блок except SyntaxError в этом файле не сработает никогда, потому что файл целиком не дошёл до запуска. Разбор таких сообщений собран в статьях про SyntaxError и ошибку отступа.
Исключение из правила — код, который компилируется во время работы программы. Разбор чужой строки через compile или exec уже перехватывается:
try:
compile("if True\n pass", "<строка>", "exec")
except SyntaxError as exc:
print(f"SyntaxError: {exc.msg}")
SyntaxError: expected ':'
Ошибки, на которых спотыкаются
Слишком широкий try. Чем больше строк внутри, тем труднее понять, какая из них сломалась, и тем выше шанс поймать чужую ошибку. Держи в try минимум, а остальное выноси в else.
Перехват ради тишины. except Exception: pass превращает падение в неправильный результат, и найти его потом дороже, чем починить исходную ошибку.
Проверка вместо перехвата и наоборот. Для редких сбоев дешевле перехват, для ожидаемых случаев дешевле проверка. Обращение к отсутствующему ключу словаря в цикле лучше решать через get, а не ловить KeyError миллион раз.
Исключение внутри except. Если обработчик сам падает, наружу уйдёт новая ошибка, а исходная останется только в трассировке как контекст. Код в except должен быть простым.
except Exception вместо конкретного типа. Ловит и опечатку в имени переменной, и битую сеть одинаково. NameError при этом выглядит как проблема с данными, хотя это баг в коде.
Частые вопросы
Чем TypeError отличается от ValueError
Первое означает неподходящий тип, второе — подходящий тип с негодным значением: int(None) даёт TypeError, int("десять") — ValueError. Правило и таблица частых сообщений — в отдельном разборе.
Чем ошибка отличается от исключения
Исключение — механизм сообщения о проблеме: интерпретатор или твой код создаёт и поднимает объект-исключение. Но логическая ошибка может просто дать неверный результат без исключения. Синтаксические ошибки тоже представлены исключениями, например SyntaxError, хотя возникают ещё до выполнения тела программы.
Как повторить код после ошибки
Оберни try/except в цикл и выйди из него через break, когда операция завершилась успешно:
while True:
try:
age = int(input("Возраст: "))
break
except ValueError:
print("Введите число ещё раз")
Здесь повторяется только ожидаемый ValueError. Для сетевых запросов и записи данных ограничивай число попыток: бесконечный цикл может зависнуть или повторить операцию дважды. Если одна политика повтора нужна нескольким функциям, вынеси её в декоратор @retry.
Как узнать текст ошибки
Через as: except ValueError as exc, дальше str(exc) или прямо в f-строке. Тип берётся как type(exc).__name__, а полная трассировка — модулем traceback.
Нужен ли except, если есть finally
Нет, конструкция try/finally без except допустима. Она означает «убрать за собой в любом случае, но ошибку не глушить»: исключение уйдёт наружу, а finally перед этим отработает.
Как поймать несколько исключений разом
Кортежем: except (ValueError, TypeError) as exc. Если каждому типу нужна своя реакция, пишут несколько блоков except от узкого к широкому.
Что делает except* со звёздочкой
Ловит группы исключений, ExceptionGroup, где за раз прилетает несколько ошибок сразу. Такое бывает при параллельном выполнении задач: несколько из них падают одновременно, и обычный except увидел бы только группу целиком.
Где потренироваться
В пути «Python: валидаторы» на Koddo обработка ошибок разбирается вместе с проверкой входных данных: сначала перехват разбора числа, потом собственное исключение, потом сбор всех нарушений вместо падения на первом. Проверить себя можно на задаче про проверку пароля: там надо не упасть на первом же нарушении, а собрать их все.