Ошибки и исключения
До сих пор сообщения об ошибках упоминались лишь вскользь, но если вы попробовали примеры, то, вероятно, уже видели их. Существует (по крайней мере) два различимых типа ошибок: синтаксические ошибки и исключения.
8.1. Синтаксические ошибки
Синтаксические ошибки, также известные как ошибки разбора, — пожалуй, наиболее частый тип жалоб, с которыми вы сталкиваетесь, когда только начинаете изучать Python:
>>> while True print('Hello world')
File "<stdin>", line 1
while True print('Hello world')
^
SyntaxError: invalid syntax
Парсер повторяет строку с ошибкой и отображает небольшую «стрелку», указывающую на самое раннее место в строке, где была обнаружена ошибка. Ошибка вызвана (или, по крайней мере, обнаружена) токеном перед стрелкой: в примере ошибка обнаружена в функции print(), так как перед ней отсутствует двоеточие (':'). Имя файла и номер строки выводятся, чтобы вы знали, где искать, если вход поступал из скрипта.
8.2. Исключения
Даже если оператор или выражение синтаксически правильны, при попытке их выполнения может возникнуть ошибка. Ошибки, обнаруженные во время выполнения, называются исключениями и не являются безусловно фатальными: вы скоро научитесь обрабатывать их в программах Python. Однако большинство исключений не обрабатываются программами и приводят к сообщениям об ошибках, как показано здесь:
>>> 10 * (1/0) Traceback (most recent call last): File "<stdin>", line 1, in <module> ZeroDivisionError: division by zero >>> 4 + spam*3 Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'spam' is not defined >>> '2' + 2 Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: Can't convert 'int' object to str implicitly
Последняя строка сообщения об ошибке указывает, что произошло. Исключения бывают разных типов, и тип печатается как часть сообщения: типами в примере являются ZeroDivisionError, NameError и TypeError. Строка, напечатанная как тип исключения, — это имя встроенного исключения, которое произошло. Это верно для всех встроенных исключений, но не обязательно верно для исключений, определенных пользователем (хотя это полезная конвенция). Стандартные имена исключений — встроенные идентификаторы (а не служебные слова).
Остальная часть строки предоставляет подробную информацию, основанную на типе исключения и его причинах.
Предшествующая часть сообщения об ошибке показывает контекст, в котором произошло исключение, в виде трассировки стека. В общем случае она содержит трассировку стека, перечисляющую строки исходного кода; однако она не будет отображать строки, считанные из стандартного ввода.
Встроенные исключения содержит список встроенных исключений и их значения.
8.3. Обработка исключений
Можно написать программы, которые обрабатывают выбранные исключения. Рассмотрим следующий пример, который запрашивает у пользователя данные до тех пор, пока не будет введено допустимое целое число, но позволяет пользователю прервать программу (используя Control-C или любую поддерживаемую операционной системой альтернативу); обратите внимание, что прерывание, созданное пользователем, сигнализируется путём повышения исключения KeyboardInterrupt.
>>> while True:
... try:
... x = int(input("Please enter a number: "))
... break
... except ValueError:
... print("Oops! That was no valid number. Try again...")
...
Оператор try работает следующим образом.
- Сначала выполняется блок try (операторы между ключевыми словами
tryиexcept). - Если исключение не возникает, блок except пропускается, и выполнение оператора
tryзавершается. - Если во время выполнения блока try возникает исключение, остальная часть блока пропускается. Затем, если его тип соответствует имени исключения после ключевого слова
except, выполняется блок except, а затем выполнение продолжается после оператораtry. - Если возникает исключение, которое не соответствует имени исключения в блоке except, оно передается внешним операторам
try; если обработчик не найден, это необработанное исключение, и выполнение останавливается с сообщением, как показано выше.
Оператор try может содержать более одного блока except, чтобы задать обработчики для различных исключений. Будет выполнен только один обработчик. Обработчики обрабатывают только исключения, возникающие в соответствующем блоке try, а не в других обработчиках того же try оператора. Блок except может указывать несколько исключений в виде кортежа в скобках, например:
... except (RuntimeError, TypeError, NameError): ... pass
Класс в блоке except совместим с исключением, если это тот же класс или его базовый класс (но не наоборот — блок except, содержащий производный класс, не совместим с базовым классом). Например, следующий код выведет B, C, D в этом порядке:
class B(Exception):
pass
class C(B):
pass
class D(C):
pass
for cls in [B, C, D]:
try:
raise cls()
except D:
print("D")
except C:
print("C")
except B:
print("B")
Обратите внимание, что если блоки except были бы переставлены (с except B первым), он вывел бы B, B, B — первый совпадающий блок except срабатывает.
Последний блок except может опустить имя исключения (исключений), чтобы служить универсальным обработчиком. Используйте это с большой осторожностью, так как легко замаскировать реальную ошибку программирования таким образом! Его также можно использовать для вывода сообщения об ошибке и последующего повторного повышения исключения (что позволит вызывающему коду обработать исключение также):
import sys
try:
f = open('myfile.txt')
s = f.readline()
i = int(s.strip())
except OSError as err:
print("OS error: {0}".format(err))
except ValueError:
print("Could not convert data to an integer.")
except:
print("Unexpected error:", sys.exc_info()[0])
raise
Оператор try … except имеет необязательный блок else, который, если присутствует, должен следовать за всеми блоками except. Он полезен для кода, который должен выполняться, если блок try не вызывает исключение. Например:
for arg in sys.argv[1:]:
try:
f = open(arg, 'r')
except OSError:
print('cannot open', arg)
else:
print(arg, 'has', len(f.readlines()), 'lines')
f.close()
Использование блока else лучше, чем добавление дополнительного кода в блок try, так как это предотвращает случайное перехват исключения, которое не было вызвано кодом, защищённым оператором try … except.
Когда возникает исключение, оно может иметь связанное значение, также известное как аргумент исключения. Наличие и тип аргумента зависят от типа исключения.
Блок except может указать переменную после имени исключения. Переменная связана с экземпляром исключения с аргументами, хранящимися в instance.args. Для удобства экземпляр исключения определяет __str__(), поэтому аргументы можно вывести непосредственно без необходимости обращения к .args. Также можно сначала создать экземпляр исключения и добавить к нему любые необходимые атрибуты.
>>> try:
... raise Exception('spam', 'eggs')
... except Exception as inst:
... print(type(inst)) # the exception instance
... print(inst.args) # arguments stored in .args
... print(inst) # __str__ allows args to be printed directly,
... # but may be overridden in exception subclasses
... x, y = inst.args # unpack args
... print('x =', x)
... print('y =', y)
...
<class 'Exception'>
('spam', 'eggs')
('spam', 'eggs')
x = spam
y = eggs
Если у исключения есть аргументы, они выводятся как последняя часть (‘детали’) сообщения об ошибке для необработанных исключений.
Обработчики исключений обрабатывают не только исключения, возникающие непосредственно в блоке try, но также и те, которые возникают внутри вызываемых (даже косвенно) функций в блоке try. Например:
>>> def this_fails():
... x = 1/0
...
>>> try:
... this_fails()
... except ZeroDivisionError as err:
... print('Handling run-time error:', err)
...
Handling run-time error: division by zero
8.4. Вызов исключений
Оператор raise позволяет программисту принудительно вызвать указанное исключение. Например:
>>> raise NameError('HiThere')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: HiThere
Единственный аргумент оператора raise указывает на исключение, которое нужно вызвать. Это должен быть либо экземпляр исключения, либо класс исключения (класс, производный от Exception). Если передается класс исключения, он будет неявно создан путем вызова его конструктора без аргументов:
raise ValueError # shorthand for 'raise ValueError()'
Если вам нужно определить, было ли вызвано исключение, но вы не собираетесь его обрабатывать, более простая форма оператора raise позволяет повторно вызвать исключение:
>>> try:
... raise NameError('HiThere')
... except NameError:
... print('An exception flew by!')
... raise
...
An exception flew by!
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
NameError: HiThere
8.5. Пользовательские исключения
Программы могут определять собственные исключения путём создания нового класса исключений (см. Классы для получения дополнительной информации о классах Python). Исключения обычно наследуются от класса Exception, либо напрямую, либо косвенно.
Классы исключений могут делать всё, что может делать любой другой класс, но обычно они простые, часто предлагая только набор атрибутов, которые позволяют извлекать информацию об ошибке обработчиками для исключения. При создании модуля, который может генерировать несколько отдельных ошибок, распространённой практикой является создание базового класса для исключений, определённых этим модулем, и наследование от него для создания конкретных классов исключений для различных условий ошибок:
class Error(Exception):
"""Base class for exceptions in this module."""
pass
class InputError(Error):
"""Exception raised for errors in the input.
Attributes:
expression -- input expression in which the error occurred
message -- explanation of the error
"""
def __init__(self, expression, message):
self.expression = expression
self.message = message
class TransitionError(Error):
"""Raised when an operation attempts a state transition that's not
allowed.
Attributes:
previous -- state at beginning of transition
next -- attempted new state
message -- explanation of why the specific transition is not allowed
"""
def __init__(self, previous, next, message):
self.previous = previous
self.next = next
self.message = message
Большинство исключений определяются с именами, оканчивающимися на «Ошибка», аналогично именованию стандартных исключений.
Многие стандартные модули определяют свои собственные исключения для сообщения об ошибках, которые могут возникать в определённых ими функциях. Дополнительная информация о классах приведена в главе Классы.
8.6. Определение действий по очистке
У оператора try есть ещё один необязательный блок, предназначенный для определения действий по очистке, которые должны выполняться в любом случае. Например:
>>> try:
... raise KeyboardInterrupt
... finally:
... print('Goodbye, world!')
...
Goodbye, world!
KeyboardInterrupt
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
Если присутствует блок finally, то блок finally выполнится последним перед завершением оператора try. Блок finally выполняется независимо от того, произошла ли ошибка в операторе try. Ниже приведены подробности о более сложных случаях, когда возникает исключение:
- Если во время выполнения блока
tryвозникает исключение, то оно может быть обработано блокомexcept. Если исключение не обрабатывается блокомexcept, оно повторно генерируется после выполнения блокаfinally. - Исключение может возникнуть во время выполнения блока
exceptилиelse. Опять же, исключение повторно генерируется после выполнения блокаfinally. - Если оператор
tryдостигает операторовbreak,continueилиreturn, то блокfinallyвыполнится непосредственно перед выполнением операторовbreak,continueилиreturn. - Если блок
finallyсодержит операторreturn, возвращаемое значение будет значением из оператораfinallyблокаreturn, а не из оператораtryблокаreturn.
Например:
>>> def bool_return(): ... try: ... return True ... finally: ... return False ... >>> bool_return() False
Более сложный пример:
>>> def divide(x, y):
... try:
... result = x / y
... except ZeroDivisionError:
... print("division by zero!")
... else:
... print("result is", result)
... finally:
... print("executing finally clause")
...
>>> divide(2, 1)
result is 2.0
executing finally clause
>>> divide(2, 0)
division by zero!
executing finally clause
>>> divide("2", "1")
executing finally clause
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in divide
TypeError: unsupported operand type(s) for /: 'str' and 'str'
Как видно, блок finally выполняется в любом случае. Исключение TypeError, возникшее при делении двух строк, не обрабатывается блоком except и, следовательно, повторно генерируется после выполнения блока finally.
В реальных приложениях блок finally полезен для освобождения внешних ресурсов (например, файлов или сетевых соединений), независимо от того, был ли успешен доступ к ресурсу.
8.7. Предопределённые действия по очистке
Некоторые объекты определяют стандартные действия по очистке, которые должны выполняться, когда объект больше не нужен, независимо от того, был ли успешным операция с этим объектом или нет. Рассмотрим следующий пример, который пытается открыть файл и вывести его содержимое на экран.
for line in open("myfile.txt"):
print(line, end="")
Проблема с этим кодом в том, что он оставляет файл открытым неопределённое время после завершения этой части кода. В простых скриптах это не проблема, но может быть проблемой для больших приложений. Оператор with позволяет использовать объекты, такие как файлы, таким образом, чтобы гарантировать их своевременную и корректную очистку.
with open("myfile.txt") as f:
for line in f:
print(line, end="")
После выполнения оператора файл f всегда закрывается, даже если при обработке строк возникла проблема. Объекты, которые, подобно файлам, предоставляют предопределённые действия по очистке, укажут на это в своей документации.
© 2001–2022 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.8/tutorial/errors.html