Spec-Zone.ru › Python 3.14

Ошибки и исключения

До сих пор сообщения об ошибках лишь упоминались, но, если вы пробовали примеры, то, вероятно, уже видели некоторые из них. Существует (по крайней мере) два различимых вида ошибок: синтаксические ошибки и исключения.

8.1. Синтаксические ошибки

Синтаксические ошибки, также известные как ошибки разбора, — пожалуй, самый распространённый вид сообщений об ошибках, с которыми вы сталкиваетесь, пока изучаете Python:

>>> while True print('Hello world')
  File "<stdin>", line 1
    while True print('Hello world')
               ^^^^^
SyntaxError: invalid syntax

Парсер повторяет строку с ошибкой и отображает небольшие стрелки, указывающие на место, где была обнаружена ошибка. Обратите внимание, что это не всегда место, которое нужно исправить. В этом примере ошибка обнаружена в функции print(), поскольку прямо перед ней пропущено двоеточие (':').

Имя файла (<stdin> в нашем примере) и номер строки выводятся, чтобы вы знали, где искать, если ввод поступил из файла.

8.2. Исключения

Даже если инструкция или выражение синтаксически корректны, попытка их выполнить может привести к ошибке. Ошибки, обнаруженные во время выполнения, называются исключениями и не всегда являются фатальными: вскоре вы узнаете, как обрабатывать их в программах на Python. Однако большинство исключений программами не обрабатывается, и в результате выводятся сообщения об ошибках, как показано здесь:

>>> 10 * (1/0)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
    10 * (1/0)
          ~^~
ZeroDivisionError: division by zero
>>> 4 + spam*3
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
    4 + spam*3
        ^^^^
NameError: name 'spam' is not defined
>>> '2' + 2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
    '2' + 2
    ~~~~^~~
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>
    raise NameError('HiThere')
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>
    raise NameError('HiThere')
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>
    open("database.sqlite")
    ~~~~^^^^^^^^^^^^^^^^^^^
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>
    raise RuntimeError("unable to handle error")
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>
    func()
    ~~~~^^
  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>
    raise RuntimeError('Failed to open database') from exc
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>
    raise RuntimeError from None
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>
    raise KeyboardInterrupt
KeyboardInterrupt

Если присутствует блок finally, блок finally выполняется последним перед завершением инструкции try. Блок finally выполняется независимо от того, приводит ли инструкция try к возникновению исключения. Ниже рассмотрены более сложные случаи, связанные с возникновением исключений:

  • Если во время выполнения блока try возникает исключение, его можно обработать блоком except. Если исключение не обработано блоком except, оно возбуждается повторно после выполнения блока finally.
  • Исключение может возникнуть во время выполнения блока except или else. И в этом случае исключение возбуждается повторно после выполнения блока finally.
  • Если блок finally выполняет инструкцию break, continue или return, исключения не возбуждаются повторно. Это может сбивать с толку, поэтому такой подход не рекомендуется. Начиная с версии 3.14 компилятор выдаёт предупреждение SyntaxWarning (см. PEP 765).
  • Если выполнение инструкции try доходит до инструкции break, continue или return, блок finally выполняется непосредственно перед выполнением инструкции break, continue или return.
  • Если блок finally содержит инструкцию return, возвращённым значением будет значение из инструкции return блока finally, а не значение из инструкции return блока try. Это может сбивать с толку, поэтому такой подход не рекомендуется. Начиная с версии 3.14 компилятор выдаёт предупреждение SyntaxWarning (см. PEP 765).

Например:

>>> 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>
    divide("2", "1")
    ~~~~~~^^^^^^^^^^
  File "<stdin>", line 3, in divide
    result = x / y
             ~~^~~
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.9. Возбуждение и обработка нескольких несвязанных исключений

Иногда необходимо сообщить о нескольких возникших исключениях. Часто это происходит в средах параллельного выполнения, когда несколько задач могли завершиться с ошибкой одновременно. Однако есть и другие случаи, когда желательно продолжить выполнение и собрать несколько ошибок, а не возбуждать первое же исключение.

Встроенный класс ExceptionGroup объединяет список экземпляров исключений, чтобы их можно было возбудить вместе. Сам он является исключением, поэтому его можно перехватить, как и любое другое исключение.

>>> def f():
...     excs = [OSError('error 1'), SystemError('error 2')]
...     raise ExceptionGroup('there were problems', excs)
...
>>> f()
  + Exception Group Traceback (most recent call last):
  |   File "<stdin>", line 1, in <module>
  |     f()
  |     ~^^
  |   File "<stdin>", line 3, in f
  |     raise ExceptionGroup('there were problems', excs)
  | ExceptionGroup: there were problems (2 sub-exceptions)
  +-+---------------- 1 ----------------
    | OSError: error 1
    +---------------- 2 ----------------
    | SystemError: error 2
    +------------------------------------
>>> try:
...     f()
... except Exception as e:
...     print(f'caught {type(e)}: {e}')
...
caught <class 'ExceptionGroup'>: there were problems (2 sub-exceptions)
>>>

Используя except* вместо except, мы можем выборочно обработать только те исключения в группе, которые соответствуют определённому типу. В следующем примере с вложенной группой исключений каждый блок except* извлекает из группы исключения определённого типа, а все остальные исключения передаются другим блокам и в конечном итоге возбуждаются повторно.

>>> def f():
...     raise ExceptionGroup(
...         "group1",
...         [
...             OSError(1),
...             SystemError(2),
...             ExceptionGroup(
...                 "group2",
...                 [
...                     OSError(3),
...                     RecursionError(4)
...                 ]
...             )
...         ]
...     )
...
>>> try:
...     f()
... except* OSError as e:
...     print("There were OSErrors")
... except* SystemError as e:
...     print("There were SystemErrors")
...
There were OSErrors
There were SystemErrors
  + Exception Group Traceback (most recent call last):
  |   File "<stdin>", line 2, in <module>
  |     f()
  |     ~^^
  |   File "<stdin>", line 2, in f
  |     raise ExceptionGroup(
  |     ...<12 lines>...
  |     )
  | ExceptionGroup: group1 (1 sub-exception)
  +-+---------------- 1 ----------------
    | ExceptionGroup: group2 (1 sub-exception)
    +-+---------------- 1 ----------------
      | RecursionError: 4
      +------------------------------------
>>>

Обратите внимание, что вложенные в группу исключений элементы должны быть экземплярами, а не типами. Это связано с тем, что на практике исключения обычно уже были возбуждены и перехвачены программой, как в следующем примере:

>>> excs = []
... for test in tests:
...     try:
...         test.run()
...     except Exception as e:
...         excs.append(e)
...
>>> if excs:
...    raise ExceptionGroup("Test Failures", excs)
...

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>
    raise TypeError('bad type')
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>
  |     raise ExceptionGroup('We have some problems', excs)
  | ExceptionGroup: We have some problems (3 sub-exceptions)
  +-+---------------- 1 ----------------
    | Traceback (most recent call last):
    |   File "<stdin>", line 3, in <module>
    |     f()
    |     ~^^
    |   File "<stdin>", line 2, in f
    |     raise OSError('operation failed')
    | OSError: operation failed
    | Happened in Iteration 1
    +---------------- 2 ----------------
    | Traceback (most recent call last):
    |   File "<stdin>", line 3, in <module>
    |     f()
    |     ~^^
    |   File "<stdin>", line 2, in f
    |     raise OSError('operation failed')
    | OSError: operation failed
    | Happened in Iteration 2
    +---------------- 3 ----------------
    | Traceback (most recent call last):
    |   File "<stdin>", line 3, in <module>
    |     f()
    |     ~^^
    |   File "<stdin>", line 2, in f
    |     raise OSError('operation failed')
    | OSError: operation failed
    | Happened in Iteration 3
    +------------------------------------
>>>

© 2001 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.14/tutorial/errors.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API