try/except в Python: обработка исключений
Как работает try/except в Python: else и finally, несколько исключений, as e, raise и raise from, свои исключения, contextlib.suppress и except* с примерами.
Содержание · 10 разделов
- Синтаксис try/except/else/finally
- Зачем нужен else
- Зачем нужен finally
- Как перехватить несколько исключений
- as e: доступ к объекту исключения
- raise: выбросить и перевыбросить исключение
- raise from: цепочка исключений
- Свои исключения
- contextlib.suppress: игнорировать ожидаемую ошибку
- ExceptionGroup и except* (Python 3.11+)
- Чего не делать
- Голый except
- Проглатывание ошибок
- Слишком широкий try
- Исключения вместо простой проверки
- Частые вопросы
- Можно ли использовать try без except?
- Как вывести полный traceback в except?
- Что лучше: return в finally или нет?
- Как поймать ModuleNotFoundError?
- Как повторять ввод, пока пользователь не введёт правильное значение?
- Замедляет ли try/except программу?
- Итог
Конструкция try/except в Python перехватывает исключения: код, который может упасть, помещают в блок try, а реакцию на ошибку — в блок except. Если исключение произошло и подходит под указанный тип, программа не завершается, а выполняет обработчик.
try:
age = int(input("Возраст: "))
except ValueError:
print("Введите число")
Если пользователь введёт abc, int() выбросит ValueError, и вместо traceback программа напечатает Введите число. Ниже — полный синтаксис с else и finally, перехват нескольких исключений, raise from, собственные классы исключений и ошибки, которых стоит избегать. Примеры проверены на Python 3.12.
Синтаксис try/except/else/finally
Полная форма содержит четыре блока, обязательны только try и хотя бы один except или finally:
try:
... # код, который может выбросить исключение
except ValueError:
... # выполняется, если в try возник ValueError
else:
... # выполняется, если в try исключений не было
finally:
... # выполняется всегда: и после ошибки, и без неё
Порядок выполнения на примере:
def parse_age(raw):
try:
age = int(raw)
except ValueError:
print(f"Не число: {raw!r}")
return None
else:
print("Преобразовано без ошибок")
return age
finally:
print("finally выполняется всегда")
print(parse_age("42"))
print(parse_age("сорок"))
Преобразовано без ошибок
finally выполняется всегда
42
Не число: 'сорок'
finally выполняется всегда
None
finally срабатывает даже тогда, когда из try, except или else уже выполняется return, — перед фактическим выходом из функции.
Зачем нужен else
В try стоит держать только ту строку, которая может выбросить ожидаемое исключение. Остальной код «при успехе» выносят в else: так except случайно не перехватит ошибку из кода, который вы не собирались защищать.
Зачем нужен finally
finally нужен для освобождения ресурсов: закрыть файл или соединение, снять блокировку, удалить временные данные. Для файлов и многих других ресурсов удобнее контекстный менеджер with — он делает то же самое автоматически:
with open("data.txt", encoding="utf-8") as f:
text = f.read()
# файл закрыт, даже если внутри with возникло исключение
Как перехватить несколько исключений
Несколько типов с одинаковой обработкой перечисляют кортежем:
def safe_div(a, b):
try:
return a / b
except (ZeroDivisionError, TypeError) as e:
print(f"{type(e).__name__}: {e}")
return None
safe_div(1, 0)
safe_div(1, "2")
ZeroDivisionError: division by zero
TypeError: unsupported operand type(s) for /: 'int' and 'str'
С Python 3.14 (PEP 758) скобки можно не писать, если не нужен as: except ZeroDivisionError, TypeError:. С as e кортеж в скобках по-прежнему обязателен.
Если обработка разная — пишут несколько блоков except. Python проверяет их сверху вниз и выполняет первый подходящий, поэтому более конкретные исключения ставят выше общих:
import json
try:
with open("config.json", encoding="utf-8") as f:
config = json.load(f)
except FileNotFoundError:
config = {}
except json.JSONDecodeError as e:
print(f"Ошибка в config.json: {e}")
raise
except перехватывает и все подклассы указанного исключения: except LookupError поймает и KeyError, и IndexError. Иерархия встроенных исключений показана в руководстве «Ошибки в Python: как читать traceback».
as e: доступ к объекту исключения
Конструкция except Тип as e сохраняет исключение в переменную. У него есть сообщение, тип и аргументы:
try:
int("x")
except ValueError as e:
print(e) # → invalid literal for int() with base 10: 'x'
print(type(e).__name__) # → ValueError
print(e.args) # → ("invalid literal for int() with base 10: 'x'",)
Переменная e существует только внутри блока except — после него Python её удаляет:
try:
x = int("abc")
except ValueError as e:
print("Ошибка:", e)
print(e)
Ошибка: invalid literal for int() with base 10: 'abc'
Traceback (most recent call last):
File "C:\projects\shop\t9.py", line 5, in <module>
print(e)
^
NameError: name 'e' is not defined
Если исключение нужно позже, присвойте его другой переменной внутри except.
raise: выбросить и перевыбросить исключение
raise выбрасывает исключение явно — например, когда функция получила недопустимые данные:
def set_discount(percent):
if not 0 <= percent <= 100:
raise ValueError(f"Скидка должна быть от 0 до 100, получено {percent}")
...
raise без аргументов внутри except перевыбрасывает текущее исключение с исходным traceback. Это частый приём «залогировать и передать дальше»:
import logging
log = logging.getLogger(__name__)
def read_config(path):
try:
with open(path, encoding="utf-8") as f:
return f.read()
except FileNotFoundError:
log.error("Нет файла конфигурации %s", path)
raise
raise from: цепочка исключений
Когда вы перехватываете низкоуровневую ошибку и выбрасываете вместо неё свою, связывайте их через from e — так в traceback сохранится первопричина:
import json
def load_config(text):
try:
return json.loads(text)
except json.JSONDecodeError as e:
raise ValueError("Некорректный конфиг") from e
Traceback покажет оба исключения, разделённые строкой The above exception was the direct cause of the following exception:. Если исходное исключение действительно не важно для читателя, его можно скрыть через from None:
def get_price(prices, name):
try:
return prices[name]
except KeyError:
raise ValueError(f"Неизвестный товар: {name}") from None
Traceback (most recent call last):
File "C:\projects\shop\prices.py", line 8, in <module>
get_price({"apple": 100}, "banana")
File "C:\projects\shop\prices.py", line 5, in get_price
raise ValueError(f"Неизвестный товар: {name}") from None
ValueError: Неизвестный товар: banana
Без from Python тоже покажет оба исключения, но с фразой During handling of the above exception, another exception occurred: — так выглядит случайная ошибка внутри обработчика, а не осознанная замена.
Свои исключения
Собственное исключение — это класс, унаследованный от Exception. От BaseException наследуют только системные исключения вроде KeyboardInterrupt и SystemExit, и своё исключение от него делать не нужно. Для приложения удобно завести базовый класс и наследовать от него конкретные ошибки:
class AppError(Exception):
"""Базовое исключение приложения."""
class NotFoundError(AppError):
pass
class InsufficientFundsError(AppError):
def __init__(self, balance, amount):
super().__init__(f"Баланс {balance}, а списать нужно {amount}")
self.balance = balance
self.amount = amount
def withdraw(balance, amount):
if amount > balance:
raise InsufficientFundsError(balance, amount)
return balance - amount
try:
withdraw(100, 250)
except InsufficientFundsError as e:
print(e) # → Баланс 100, а списать нужно 250
print(e.amount - e.balance) # → 150
Вызывающий код может ловить конкретную ошибку (except NotFoundError) или все ошибки приложения разом (except AppError). Названия классов по соглашению заканчиваются на Error.
contextlib.suppress: игнорировать ожидаемую ошибку
Когда исключение действительно не важно, вместо try/except: pass можно написать короче:
import os
from contextlib import suppress
with suppress(FileNotFoundError):
os.remove("cache.tmp")
Это эквивалент:
try:
os.remove("cache.tmp")
except FileNotFoundError:
pass
Используйте suppress только для конкретных и заведомо безопасных исключений.
ExceptionGroup и except* (Python 3.11+)
С Python 3.11 можно выбросить сразу несколько несвязанных исключений, упаковав их в ExceptionGroup, — например, все ошибки валидации формы или ошибки нескольких параллельных задач asyncio.TaskGroup. Для обработки групп появился синтаксис except*: каждый блок обрабатывает свою часть группы.
def validate(user):
errors = []
if not user.get("name"):
errors.append(ValueError("пустое имя"))
if not isinstance(user.get("age"), int):
errors.append(TypeError("возраст должен быть числом"))
if errors:
raise ExceptionGroup("ошибки валидации", errors)
try:
validate({"name": "", "age": "20"})
except* ValueError as eg:
for err in eg.exceptions:
print("ValueError:", err)
except* TypeError as eg:
for err in eg.exceptions:
print("TypeError:", err)
ValueError: пустое имя
TypeError: возраст должен быть числом
В отличие от обычного except, здесь могут сработать несколько блоков. В одном try нельзя смешивать except и except*. В повседневном коде группы исключений встречаются редко — достаточно знать, что они существуют.
Чего не делать
Голый except
# Плохо
try:
process()
except:
pass
Голый except: перехватывает вообще всё, включая KeyboardInterrupt (Ctrl+C) и SystemExit (sys.exit()), — программу становится трудно даже остановить. Если нужен «перехват всего», пишите except Exception, но и это оправдано только на верхнем уровне: в обработчике веб-запроса, в цикле фоновой задачи, — и обязательно с логированием.
Проглатывание ошибок
# Плохо: ошибка исчезла, данные молча не сохранились
try:
save_order(order)
except Exception:
pass
Пустой обработчик превращает понятный traceback в загадочный баг: программа «работает», но делает не то. Минимум — залогировать исключение с traceback:
import logging
log = logging.getLogger(__name__)
try:
save_order(order)
except Exception:
log.exception("Не удалось сохранить заказ %s", order.id)
raise
Слишком широкий try
# Плохо: KeyError может прилететь из любой из строк
try:
user = users[user_id]
discount = settings["discount"]
total = calc_total(user, discount)
except KeyError:
total = 0
Оборачивайте в try только ту операцию, ошибку которой вы обрабатываете. Остальное — до try или в else.
Исключения вместо простой проверки
Для словарей dict.get(key, default) часто читается лучше, чем try/except KeyError. Но в целом стиль Python допускает оба подхода: «проще попросить прощения, чем разрешения» (EAFP) — нормальная практика, когда проверка заранее сложна или ненадёжна, как с существованием файла. Методы словаря описаны в статье словари в Python.
Частые вопросы
Можно ли использовать try без except?
Да, если есть finally: try/finally гарантирует выполнение кода очистки, но исключение после этого продолжит всплывать.
Как вывести полный traceback в except?
Используйте logging.exception("сообщение") или traceback.print_exc() из модуля traceback. Обе функции печатают traceback текущего исключения и не останавливают программу.
Что лучше: return в finally или нет?
Не пишите return в finally: он отменяет исключение, которое возникло в try, и ошибка тихо пропадает. То же делают break и continue, которые выходят из finally. С Python 3.14 (PEP 765) компилятор выдаёт на такой код SyntaxWarning: 'return' in a 'finally' block; предупреждение появляется при компиляции файла, а не при каждом выполнении.
Как поймать ModuleNotFoundError?
Так же, как любое исключение: except ModuleNotFoundError. Это используют для необязательных зависимостей. Но если импорт обязателен, ошибку нужно исправлять, а не прятать — см. статью ModuleNotFoundError: No module named.
Как повторять ввод, пока пользователь не введёт правильное значение?
Поместите try/except в бесконечный цикл и выходите из него через break после успешного преобразования:
while True:
try:
age = int(input("Возраст: "))
break
except ValueError:
print("Нужно целое число, попробуйте ещё раз")
Для повторных попыток сетевых запросов удобнее декоратор — пример retry есть в статье о декораторах.
Замедляет ли try/except программу?
Если исключение не возникло — практически нет: с Python 3.11 вход в блок try ничего не стоит («zero-cost» исключения). Дорого само исключение, поэтому try/except не стоит использовать для ситуации, которая случается на каждой итерации большого цикла, — там быстрее обычная проверка.
Итог
try/except перехватывает конкретные исключения, else выполняется при успехе, finally — всегда. Перехватывайте узкие типы, перевыбрасывайте через raise или raise ... from e, заводите свои классы исключений от Exception и не глотайте ошибки молча. Как читать сами сообщения об ошибках, рассказано в руководстве «Ошибки в Python: как читать traceback»; весь кластер — в разделе «Ошибки и исключения».
- #Python
- #try except
- #Исключения
- #raise
- #Обработка ошибок