Python Try...Except
Изучите блоки try, except, else и finally в Python с примерами. Обработка ZeroDivisionError, ValueError, FileNotFoundError и других исключений.
Когда Python встречает ошибку во время выполнения, он генерирует исключение — объект, описывающий, что пошло не так. Без обработки исключение немедленно завершает программу. Инструкция try...except позволяет перехватывать исключения, реагировать на них корректно и продолжать выполнение программы.
В этой главе рассматриваются:
- Блок
try/except— перехват конкретного исключения - Получение сведений об исключении с помощью
as - Несколько клауз
exceptи перехват нескольких исключений одновременно - Блок
else— код, выполняющийся только при отсутствии исключений - Блок
finally— код очистки, выполняющийся всегда - Распространённые встроенные исключения и когда они возникают
- Повторное возбуждение исключений
- Лучшие практики и типичные ошибки
Базовый блок try...except
Оберните код, который может завершиться ошибкой, в блок try. Если Python генерирует исключение, выполнение переходит в соответствующий блок except вместо аварийного завершения программы.
Вывод:
Error: division by zeroPython пытается выполнить деление, генерирует ZeroDivisionError, и блок except обрабатывает его. Клауза as e привязывает объект исключения к переменной e, чтобы вы могли проверить или записать сообщение в журнал.
Если блок try выполняется успешно, блок except пропускается полностью.
Перехват исключения определённого типа
Всегда указывайте тип ожидаемого исключения. Перехват именованного типа делает намерение явным и предотвращает случайное сокрытие несвязанных ошибок.
try:
number = int("abc")
except ValueError as e:
print(f"Could not convert: {e}")Вывод:
Could not convert: invalid literal for int() with base 10: 'abc'int("abc") генерирует ValueError, поскольку "abc" не является допустимым целым числом. Указание ValueError в клаузе except означает, что любое другое неожиданное исключение будет по-прежнему распространяться и проявляться как ошибка, а не замалчиваться.
Несколько клауз except
Один блок try может содержать несколько клауз except — Python проверяет их сверху вниз и выполняет первое совпадение.
def safe_index(items, index):
try:
return items[index]
except IndexError:
print("Index out of range.")
except TypeError:
print("Index must be an integer.")
safe_index([1, 2, 3], 10) # IndexError
safe_index([1, 2, 3], "a") # TypeErrorВывод:
Index out of range.
Index must be an integer.Порядок важен: размещайте более специфичные типы исключений перед более широкими, чтобы конкретный обработчик выполнялся первым.
Перехват нескольких исключений в одной клаузе
Когда два или более исключений требуют одинаковой реакции, сгруппируйте их в кортеж:
try:
value = int("not-a-number")
except (ValueError, TypeError) as e:
print(f"Input error: {e}")Вывод:
Input error: invalid literal for int() with base 10: 'not-a-number'Блок else
Блок else выполняется только тогда, когда блок try завершается без генерации исключений. Используйте его для кода, который должен выполняться при успехе, но сам не нуждается в защите от ошибок:
try:
result = 10 / 2
except ZeroDivisionError:
print("Cannot divide by zero.")
else:
print(f"Division succeeded. Result: {result}")Вывод:
Division succeeded. Result: 5.0Размещение логики успешного пути в else (а не в конце try) предотвращает случайный перехват исключений, которые может генерировать сам код успешного пути.
Блок finally
Блок finally выполняется в любом случае — независимо от того, успешно ли завершился блок try, было ли исключение перехвачено или оно распространяется без обработки. Используйте его для очистки: закрытия файлов, освобождения блокировок или отключения от базы данных.
try:
result = 10 / 0
except ZeroDivisionError:
print("Error caught.")
finally:
print("This always runs — cleanup goes here.")Вывод:
Error caught.
This always runs — cleanup goes here.Даже если закомментировать блок except, finally всё равно выполнится перед тем, как Python распространит исключение.
Полная структура с первого взгляда
try:
# code that may raise an exception
except SomeException as e:
# handle the exception
except (AnotherError, YetAnother):
# handle either of these
else:
# runs only when try succeeded
finally:
# always runsРаспространённые встроенные исключения
| Исключение | Когда возникает |
|---|---|
ZeroDivisionError | Деление или остаток от деления на ноль |
ValueError | Правильный тип, неверное значение (например, int("abc")) |
TypeError | Операция применена к неправильному типу |
IndexError | Индекс последовательности выходит за пределы диапазона |
KeyError | Ключ словаря не найден |
FileNotFoundError | Файл или каталог не существует |
AttributeError | Объект не имеет такого атрибута |
ImportError | Модуль не может быть импортирован |
NameError | Имя переменной не определено |
Все они наследуются от базового класса Exception. Можно перехватить Exception для обработки любого из них в одной клаузе, но предпочтительнее использовать конкретные типы там, где это возможно.
Обработка отсутствующего файла
try:
with open("data.txt", "r") as file:
content = file.read()
except FileNotFoundError:
print("Error: File not found.")Вывод (когда data.txt не существует):
Error: File not found.Повторное возбуждение исключения
Иногда нужно записать исключение в журнал или выполнить частичную очистку, но при этом позволить ему распространиться к вызывающему коду. Используйте голый raise внутри блока except:
def process():
try:
result = 10 / 0
except ZeroDivisionError:
print("Logging error...")
raise # re-raises the original ZeroDivisionError
try:
process()
except ZeroDivisionError as e:
print(f"Outer handler caught: {e}")Вывод:
Logging error...
Outer handler caught: division by zeroГолый raise сохраняет исходную трассировку, поэтому отладка не становится сложнее, чем без обработчика.
Перехват базового класса Exception
Можно использовать Exception в качестве обработчика-перехватчика для любой ошибки, не завершающей систему:
try:
result = 10 / 0
except Exception as e:
print(f"An error occurred: {type(e).__name__}: {e}")Вывод:
An error occurred: ZeroDivisionError: division by zerotype(e).__name__ возвращает конкретное имя класса, даже когда перехват выполняется через базовый класс. Это полезно в обработчиках верхнего уровня, где нужно записывать каждую неожиданную ошибку.
Голый except — и почему его следует избегать
Голый except (без типа исключения) перехватывает буквально всё, включая KeyboardInterrupt (Ctrl+C) и SystemExit, что делает программу сложной для прерывания:
# Avoid this pattern
try:
result = 10 / 0
except:
print("Some error occurred")Предпочтительнее использовать except Exception, если нужен широкий перехват, поскольку он всё равно позволяет KeyboardInterrupt и SystemExit распространяться в штатном режиме.
Лучшие практики
- Будьте конкретны. Перехватывайте наиболее узкий тип исключения, который имеет смысл.
- Не замалчивайте исключения. Как минимум запишите ошибку в журнал; никогда не оставляйте блок
exceptпустым. - Используйте
elseдля кода успешного пути. Это делает блокtryкак можно меньшим. - Используйте
finallyдля очистки. Или, что ещё лучше, используйте инструкциюwith— см. Python with Statement. - Избегайте голого
except. Используйтеexcept Exception, если нужна широкая сеть. - Повторно возбуждайте исключение там, где это уместно. Если вы не можете полностью обработать ошибку, позвольте ей распространиться с помощью
raise.
Для намеренного возбуждения исключений и создания собственных классов исключений см. Python raise and Custom Exceptions.