Spec-Zone.ru › Python 3.13

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

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

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, но и исключения, возникшие внутри функций, вызываемых (даже косвенно) из блока 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, исключения не возбуждаются повторно.
  • Если оператор 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>
    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'>: e
>>>

Используя 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–2025 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.13/tutorial/errors.html

Spec-Zone.ru

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