Ошибки и исключения
До сих пор сообщения об ошибках упоминались лишь мимоходом, но если вы пробовали примеры, вы, вероятно, уже видели некоторые. Существует (по крайней мере) два различимых типа ошибок: синтаксические ошибки и исключения.
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, оно передаётся в более внешние операторы
try; если обработчик не найден, это необработанное исключение, и выполнение завершается сообщением, как показано выше.
Оператор try может содержать более одного блока except, чтобы указать обработчики для различных исключений. Будет выполнен только один обработчик. Блоки 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. Цепочки исключений
Оператор raise допускает необязательный аргумент from, который позволяет создавать цепочки исключений. Например:
# exc must be exception instance or None. raise RuntimeError from exc
Это может быть полезно при преобразовании исключений. Например:
>>> def func():
... raise IOError
...
>>> try:
... func()
... except IOError 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
OSError
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
Цепочки исключений автоматически создаются при возникновении исключения внутри блока except или finally. Цепочки исключений можно отключить, используя конструкцию 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–2022 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.9/tutorial/errors.html