Ошибки и исключения
До сих пор сообщения об ошибках упоминались лишь вскользь, но если вы пробовали примеры, вы, вероятно, видели некоторые. Существуют (по крайней мере) два различимых типа ошибок: синтаксические ошибки и исключения.
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 only concatenate str (not "int") to str
Последняя строка сообщения об ошибке указывает, что произошло. Исключения бывают разных типов, и тип печатается как часть сообщения: типами в примере являются 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. - Если возникает исключение, которое не соответствует исключению, указанному в блоке 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 запускается.
Все исключения наследуются от BaseException, и поэтому его можно использовать в качестве универсального. Используйте это с большой осторожностью, так как легко скрыть реальную ошибку программирования таким образом! Его также можно использовать для вывода сообщения об ошибке и затем повторного повышения исключения (что позволит вызывающему коду также обработать исключение):
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 BaseException as err:
print(f"Unexpected {err=}, {type(err)=}")
raise
В качестве альтернативы, последний блок except может опустить имя исключения(й), но значение исключения в этом случае необходимо извлечь из sys.exc_info()[1].
Оператор 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. Цепочки исключений
Если во время обработки исключения (внутри блока except) возникает ещё одно исключение, то первоначальное исключение будет прикреплено к нему и включено в сообщение об ошибке:
>>> try:
... open("database.sqlite")
... except OSError:
... raise RuntimeError("unable to handle error")
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
FileNotFoundError: [Errno 2] No such file or directory: 'database.sqlite'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
RuntimeError: unable to handle error
Для указания того, что исключение является прямым следствием другого, оператор raise позволяет необязательный from аргумент:
# exc must be exception instance or None. raise RuntimeError from exc
Это может быть полезно при преобразовании исключений. Например:
>>> def func():
... raise ConnectionError
...
>>> try:
... func()
... except ConnectionError as exc:
... raise RuntimeError('Failed to open database') from exc
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
File "<stdin>", line 2, in func
ConnectionError
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
RuntimeError: Failed to open database
Он также позволяет отключить автоматическую цепочку исключений, используя from None конструкцию:
>>> try:
... open('database.sqlite')
... except OSError:
... raise RuntimeError from None
...
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
RuntimeError
Дополнительную информацию об механизме цепочки см. в разделе Встроенные исключения.
8.6. Пользовательские исключения
Программы могут задавать свои исключения, создавая новый класс исключений (см. Классы для получения дополнительной информации о классах Python). Исключения обычно наследуются от класса Exception, либо прямо, либо косвенно.
Можно определить классы исключений, которые могут делать всё то же, что и другие классы, но их обычно делают простыми, часто предоставляя лишь ряд атрибутов, которые позволяют извлечь информацию об ошибке обработчиками исключений.
Большинство исключений определены с именами, оканчивающимися на «Ошибка», аналогично стандартным исключениям.
Многие стандартные модули определяют свои собственные исключения для сообщения об ошибках, которые могут возникнуть в определённых ими функциях. Более подробная информация о классах приведена в главе Классы.
8.7. Определение действий по очистке
У оператора 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. - Если блок
finallyвыполняет операторыbreak,continueилиreturn, исключения не повторно поднимаются. - Если оператор
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.8. Предопределенные действия по очистке
Некоторые объекты определяют стандартные действия по очистке, которые должны выполняться, когда объект больше не нужен, независимо от того, было ли успешным или неуспешным выполнение операции с объектом. Рассмотрим следующий пример, в котором пытаются открыть файл и вывести его содержимое на экран.
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–2023 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.10/tutorial/errors.html