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

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

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

Синтаксические ошибки (Syntax Errors), также известны как ошибки грамматического разбора (parsing errors), являются, пожалуй, наиболее распространенным видом жалоб пока вы все еще изучаете Python:

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

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

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

Даже если оператор или выражение синтаксически правильно, все-равно может возникнуть ошибка при попытке его выполнить. Ошибки, обнаруженные во время выполнения, называются исключениями (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: Can't convert 'int' object to str implicitly

В последней строке сообщения об ошибке указывается, что произошло. Исключения бывают разных типов, тип выводится как часть сообщения: типами в примере являются ZeroDivisionError (docs.python.org/3/library/exceptions.html#ZeroDivisionError), NameError (docs.python.org/3/library/exceptions.html#NameError) и TypeError (docs.python.org/3/library/exceptions.html#TypeError). Название случившегося исключения выводится как название типа встроенного исключения. Это верно для всех встроенных исключений, но не обязательно для пользовательских исключений (хотя это полезное соглашение). Имена стандартных исключений представляют собой встроенные идентификаторы (не зарезервированные ключевые слова).

Остальная часть строки сообщает детали на основе типа исключения и того, что стало его причиной.

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

Встроенные исключения (docs.python.org/3/library/exceptions.html#bltin-exceptions) перечисляет встроенные исключения и их значения.

8.3. Обработка исключений

Существует возможность писать программы, которые обрабатывают выбранные исключения. Посмотрите на следующий пример, который запрашивает у пользователя ввод до тех пор, пока он не введет допустимое целое число, но позволяет пользователю прервать программу (с помощью Control-C или того, что поддерживает конкретная операционная система); обратите внимание, что сгенерированное пользователем прерывание возникает как исключение KeyboardInterrupt (docs.python.org/3/library/exceptions.html#KeyboardInterrupt) (клавиатурное прерывание).

>>> while True:
...     try:
...         x = int(input("Please enter a number: "))
...         break
...     except ValueError:
...         print("Oops!  That was no valid number.  Try again...")
...

Оператор try (docs.python.org/3/reference/compound_stmts.html#try) работает следующим образом.

  • Сначала выполняется блок try (выражение(я) между ключевыми словами try (docs.python.org/3/reference/compound_stmts.html#try) и except (docs.python.org/3/reference/compound_stmts.html#except)).
  • Если исключение не произошло, блок except пропускается и выполнение оператора try закончено.
  • Если во время выполнения содержимого try возникает исключение, выражения ниже пропускаются. Затем, если тип возникшего исключения соответствует имени исключения после ключевого слова except, содержимое except выполняется, и затем выполнение продолжается после всего оператора try.
  • Если происходит исключение, которое не соответствует имени исключения в строке 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 B), то было бы выведено B, B, B, так как сработало бы первое сопоставление блока except.

В последнем пункте 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__() (docs.python.org/3/reference/datamodel.html#object.__str__), так что аргументы можно вывести сразу, без того, чтобы ссылаться на .args. Также возможно проиллюстрировать (instantiate) исключение прежде, чем сгенерировать его и добавлять какие-либо атрибуты, как пожелаете.

>>> try:
...    raise Exception('spam', 'eggs')
... except Exception as inst:
...    print(type(inst))    # экземпляр исключения
...    print(inst.args)     # аргументы хранимые в .args
...    print(inst)          # __str__ позволяет вывести аргументы сразу,
...                         # но могут быть переведены в подклассы исключений
...    x, y = inst.args     # распаковка args
...    print('x =', x)
...    print('y =', y)
...
<class 'Exception'>
('spam', 'eggs')
('spam', 'eggs')
x = spam
y = eggs

Если у исключения есть аргументы, они выводятся как последняя часть (‘detail’ - подробность) сообщения для необработанных исключений.

Обработчики исключений не только обрабатывают исключения, которые происходят непосредственно в ветке 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 (docs.python.org/3/reference/simple_stmts.html#raise) позволяет программисту сгенерировать указанное исключение. Например:

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

В единственном аргументе raise указывается исключение, которое будет вызвано. Оно должно быть либо экземпляром исключения, либо классом исключения (дочерним по отношению к Exception (docs.python.org/3/library/exceptions.html#Exception)). Если передается класс исключения, то будет неявно создан экземпляр объекта, вызовом его конструктора без аргументов:

raise ValueError  # сокращение для '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 ?
NameError: HiThere

8.5. Исключения, определяемые пользователем

В программах можно создавать свои собственные исключения, создав новый класс исключения (см. Классы для подробной информации о классах Python). Исключения должны быть обычно получены от класса Exception (docs.python.org/3/library/exceptions.html#Exception), прямо или опосредованно.

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

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", похоже на именование стандартных исключений.

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

8.6. Определение очищающих действий

Оператор try (docs.python.org/3/reference/compound_stmts.html#try) имеет другую дополнительную ветку, которая предназначена для определения clean-up (очищающих - прим. пер.) действий, которые должны быть выполнены при любых обстоятельствах. Например:

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

Содержимое 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.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 ?
  File "<stdin>", line 3, in divide
TypeError: unsupported operand type(s) for /: 'str' and 'str'

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

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

8.7. Предопределенные очищающие действия

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

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

Проблема, связанная с этим кодом заключается в том, что файл остается открытым на неопределенное количество времени уже после того, как эта часть кода завершит выполнение. Это не является проблемой в простых скриптах, но может быть проблемой для больших приложений. Оператор with (docs.python.org/3/reference/compound_stmts.html#with) позволяет таким объектам как файлы быть использованными таким образом, что всегда обеспечит завершающие действия с ними сразу и корректно.

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

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

Создано

Обновлено