Ошибки и исключения
До сих пор сообщения об ошибках упоминались лишь мимоходом, но если вы пробовали примеры, то, вероятно, видели некоторые из них. Существует (по крайней мере) два различимых типа ошибок: синтаксические ошибки и исключения.
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 возникает исключение, остальная часть блока 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, потому что это предотвращает случайное перехват исключения, которое не было вызвано кодом, защищённым оператором else … except.
Обработчики исключений обрабатывают не только исключения, которые возникают непосредственно в блоке 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 указывает вызываемое исключение. Это должен быть либо экземпляр исключения, либо класс исключения (класс, производный от 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, возвращаемое значение будет тем, которое возвращается инструкцией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 всегда закрывается, даже если при обработке строк возникла проблема. Объекты, которые, подобно файлам, предоставляют предварительно определённые действия по очистке, указывают на это в своей документации.
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