Ошибки и исключения
До сих пор сообщения об ошибках лишь упоминались, но если вы пробовали примеры, то, вероятно, уже видели некоторые из них. Существует (по крайней мере) два различимых вида ошибок: синтаксические ошибки и исключения.
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.
При возникновении исключения с ним могут быть связаны значения, также известные как аргументы исключения. Наличие и типы аргументов зависят от типа исключения.
В блоке except после имени исключения можно указать переменную. Эта переменная связывается с экземпляром исключения, у которого обычно есть атрибут args, хранящий аргументы. Для удобства встроенные типы исключений определяют метод __str__(), который выводит все аргументы без явного обращения к .args.
>>> try:
... raise Exception('spam', 'eggs')
... except Exception as inst:
... print(type(inst)) # the exception type
... 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
Результат вызова __str__() исключения выводится в конце сообщения о необработанном исключении в виде «подробностей».
BaseException — общий базовый класс всех исключений. Один из его подклассов, Exception, является базовым классом всех нефатальных исключений. Исключения, не являющиеся подклассами Exception, обычно не обрабатываются, поскольку они используются для указания на то, что программа должна завершиться. К ним относятся SystemExit, возбуждаемое функцией sys.exit(), и KeyboardInterrupt, возбуждаемое, когда пользователь хочет прервать программу.
Exception можно использовать как универсальный обработчик, перехватывающий (почти) всё. Однако рекомендуется по возможности указывать конкретные типы исключений, которые требуется обрабатывать, и позволять неожиданным исключениям распространяться дальше.
Наиболее распространённый способ обработки Exception — вывести исключение или записать его в журнал, а затем возбудить его повторно (чтобы вызывающий код тоже мог его обработать):
import sys
try:
f = open('myfile.txt')
s = f.readline()
i = int(s.strip())
except OSError as err:
print("OS error:", err)
except ValueError:
print("Could not convert data to an integer.")
except Exception as err:
print(f"Unexpected {err=}, {type(err)=}")
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 коде.
Обработчики исключений обрабатывают не только исключения, возникающие непосредственно в блоке 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 указывает, какое исключение нужно возбудить. Это должен быть либо экземпляр исключения, либо класс исключения (класс, производный от BaseException, например 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.
Классы исключений могут делать всё то же, что и любые другие классы, но обычно их оставляют простыми и часто снабжают лишь набором атрибутов, позволяющих обработчикам извлекать сведения об ошибке.
Большинство исключений получают имена, заканчивающиеся на «Error», по аналогии с именами стандартных исключений.
Многие стандартные модули определяют собственные исключения, чтобы сообщать об ошибках, которые могут возникнуть в определённых ими функциях.
8.7. Определение действий по очистке
У инструкции try есть ещё один необязательный блок, предназначенный для определения действий по очистке, которые должны выполняться при любых обстоятельствах. Например:
>>> try:
... raise KeyboardInterrupt
... finally:
... print('Goodbye, world!')
...
Goodbye, world!
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
KeyboardInterrupt
Если присутствует блок 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, возвращаемым значением будет значение из инструкцииreturnблокаfinally, а не из инструкцииreturnблокаtry.
Например:
>>> 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 всегда закрывается, даже если при обработке строк возникла проблема. В документации объектов, которые, подобно файлам, предоставляют предопределённые действия по очистке, обычно указывается эта возможность.
8.10. Дополнение исключений примечаниями
При создании исключения для последующего возбуждения его обычно инициализируют информацией об возникшей ошибке. В некоторых случаях полезно добавить сведения после того, как исключение было перехвачено. Для этого у исключений есть метод add_note(note), принимающий строку и добавляющий её в список примечаний исключения. Стандартное форматирование трассировки стека выводит все примечания после исключения в порядке их добавления.
>>> try:
... raise TypeError('bad type')
... except Exception as e:
... e.add_note('Add some information')
... e.add_note('Add some more information')
... raise
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
TypeError: bad type
Add some information
Add some more information
>>>
Например, при сборе исключений в группу можно добавить контекстную информацию для отдельных ошибок. В следующем примере к каждому исключению в группе добавлено примечание с указанием времени его возникновения.
>>> def f():
... raise OSError('operation failed')
...
>>> excs = []
>>> for i in range(3):
... try:
... f()
... except Exception as e:
... e.add_note(f'Happened in Iteration {i+1}')
... excs.append(e)
...
>>> raise ExceptionGroup('We have some problems', excs)
+ Exception Group Traceback (most recent call last):
| File "<stdin>", line 1, in <module>
| ExceptionGroup: We have some problems (3 sub-exceptions)
+-+---------------- 1 ----------------
| Traceback (most recent call last):
| File "<stdin>", line 3, in <module>
| File "<stdin>", line 2, in f
| OSError: operation failed
| Happened in Iteration 1
+---------------- 2 ----------------
| Traceback (most recent call last):
| File "<stdin>", line 3, in <module>
| File "<stdin>", line 2, in f
| OSError: operation failed
| Happened in Iteration 2
+---------------- 3 ----------------
| Traceback (most recent call last):
| File "<stdin>", line 3, in <module>
| File "<stdin>", line 2, in f
| OSError: operation failed
| Happened in Iteration 3
+------------------------------------
>>>
© 2001–2024 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.12/tutorial/errors.html