Документация Python неофициальный перевод
Содержание страницы

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

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

Синтаксические ошибкиSyntax Errors

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

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

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

ИсключенияExceptions

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

>>> 10 * (1/0)
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
ZeroDivisionError: int division or modulo by zero
>>> 4 + spam*3
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
NameError: name 'spam' is not defined
>>> '2' + 2
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: coercing to Unicode: need string or buffer, int found

Последняя строка сообщения об ошибке указывает, что произошло. Исключения бывают разных типов, и тип выводится как часть сообщения: типы в примере – ZeroDivisionError, NameError и TypeError. Строка, выводимая как тип исключения, – это имя встроенного исключения, которое произошло. Это верно для всех встроенных исключений, но не обязательно для пользовательских (хотя это полезное соглашение). Стандартные имена исключений – встроенные идентификаторы (не зарезервированные ключевые слова).

Остальная часть строки содержит подробности в зависимости от типа исключения и того, что его вызвало.

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

Встроенные исключения содержит список встроенных исключений и их значение.

Обработка исключенийHandling Exceptions

Можно писать программы, обрабатывающие выбранные исключения. Посмотрите на следующий пример, который запрашивает у пользователя ввод до тех пор, пока не будет введено целое число, но позволяет прервать программу (с помощью 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 для указания обработчиков разных исключений. Будет выполнен не более одного обработчика. Обработчики обрабатывают только исключения, возникшие в соответствующем блоке try, а не в других обработчиках той же инструкции try. Блок except может перечислять несколько исключений в виде кортежа в скобках, например:

... except (RuntimeError, TypeError, NameError):
...     pass

Последний блок except может опускать имена исключений, чтобы служить универсальным обработчиком. Используйте это с особой осторожностью, поскольку легко скрыть настоящую ошибку программирования таким образом! Его также можно использовать для вывода сообщения об ошибке и последующего повторного возбуждения исключения (позволяя вызывающему коду также обработать исключение):

import sys

try:
    f = open('myfile.txt')
    s = f.readline()
    i = int(s.strip())
except IOError as err:
    print("I/O 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 IOError:
        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))    # экземпляр исключения
...    print(inst.args)     # аргументы, хранящиеся в .args
...    print(inst)          # __str__ позволяет выводить args напрямую, но может быть переопределён в подклассах исключений
...                         # но может быть переопределён в подклассах исключений
...    x, y = inst.args     # распаковать 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: int division or modulo by zero

Возбуждение исключенийRaising Exceptions

Инструкция raise позволяет программисту принудительно вызвать указанное исключение. Например:

>>> raise NameError('HiThere')
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
NameError: HiThere

Единственный аргумент raise указывает исключение, которое нужно возбудить. Это должен быть либо экземпляр исключения, либо класс исключения (класс, производный от Exception).

Если нужно определить, было ли возбуждено исключение, но вы не собираетесь его обрабатывать, более простая форма инструкции 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 ?
NameError: HiThere

Пользовательские исключенияUser-defined Exceptions

Программы могут определять собственные исключения, создавая новый класс исключения. Исключения, как правило, должны быть производными от класса Exception, прямо или косвенно. Например:

>>> class MyError(Exception):
...     def __init__(self, value):
...         self.value = value
...     def __str__(self):
...         return repr(self.value)
...
>>> try:
...     raise MyError(2*2)
... except MyError as e:
...     print('My exception occurred, value:', e.value)
...
My exception occurred, value: 4
>>> raise MyError('oops!')
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
__main__.MyError: 'oops!'

В этом примере стандартный метод __init__() класса Exception был переопределён. Новое поведение просто создаёт атрибут value. Это заменяет стандартное поведение, создающее атрибут args.

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

class Error(Exception):
    """Base class for exceptions in this module."""
    pass

class InputError(Error):
    """Exception raised for errors in the input.

    Attributes:
        expression -- input expression in which the error occurred
        message -- explanation of the error
    """

    def __init__(self, expression, message):
        self.expression = expression
        self.message = message

class TransitionError(Error):
    """Raised when an operation attempts a state transition that's not
    allowed.

    Attributes:
        previous -- state at beginning of transition
        next -- attempted new state
        message -- explanation of why the specific transition is not allowed
    """

    def __init__(self, previous, next, message):
        self.previous = previous
        self.next = next
        self.message = message

Большинство исключений определяются с именами, оканчивающимися на «Error», как и стандартные исключения.

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

Определение действий по очисткеDefining Clean-up Actions

Инструкция try имеет еще один необязательный блок, предназначенный для определения действий по очистке, которые должны выполняться в любых обстоятельствах. Например:

>>> try:
...     raise KeyboardInterrupt
... finally:
...     print('Goodbye, world!')
...
Goodbye, world!
Traceback (most recent call last):
  File "<stdin>", line 2, in ?
KeyboardInterrupt

Блок finally всегда выполняется перед выходом из инструкции try, независимо от того, произошло исключение или нет. Когда исключение произошло в блоке try и не было обработано блоком except (или произошло в блоке except или else), оно возбуждается повторно после выполнения блока finally. Блок finally также выполняется «на выходе», когда любой другой блок инструкции try покидается через инструкцию break, continue или return. Более сложный пример:

>>> 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
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 ?
  File "<stdin>", line 3, in divide
TypeError: unsupported operand type(s) for /: 'str' and 'str'

Как видите, блок finally выполняется в любом случае. TypeError, возбужденное при делении двух строк, не обрабатывается блоком except и поэтому возбуждается повторно после выполнения блока finally.

В реальных приложениях блок finally полезен для освобождения внешних ресурсов (таких как файлы или сетевые соединения), независимо от того, было ли использование ресурса успешным.

Предопределённые действия по очисткеPredefined Clean-up Actions

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

for line in open("myfile.txt"):
    print(line)

Проблема этого кода в том, что он оставляет файл открытым на неопределенное время после завершения выполнения этой части кода. В простых скриптах это не проблема, но в больших приложениях может стать проблемой. Инструкция with позволяет использовать такие объекты, как файлы, так, чтобы они всегда своевременно и корректно закрывались.

with open("myfile.txt") as f:
    for line in f:
        print(line)

После выполнения инструкции файл f всегда закрывается, даже если при обработке строк возникла проблема. Объекты, которые, подобно файлам, предоставляют предопределённые действия по очистке, сообщают об этом в своей документации.