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



Программируя на Питоне, вы наверняка встречались с различными ошибками. Самые простые из них – синтаксические. Такие ошибки обнаруживаются во время трансляции кода на Питоне и появляются чаще всего из-за невнимательности программиста. Второй тип ошибок – ошибки пользователя или операционной системы, которые называются иначе исключительными ситуациями, например, деление на нуль. Необходимо постараться предусмотреть возможность обработки таких ошибок (о том, как это сделать будет рассказано далее). Самые сложные ошибки – логические, которые возникают из-за неправильной(то есть непредусмотренной создателем) работы программы. Способов избежать таких ошибок нет – надо быть внимательным при написании своего кода, правильно его отлаживать и писать максимально комментированным, чтобы упростить обнаружение ошибок, как самим автором, так и теми, кто собирается этот код модифицировать.

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

Ошибки такого типа обычно находятся интерпретатором:

>>> while 1 print 'Hello world'

File "<stdin>", line 1, in ?

while 1 print 'Hello world'

^

SyntaxError: invalid syntax #Неверный синтаксис команды


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


Исключительные ситуации.

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

>>> 10 * (1/0)

Traceback (most recent call last):

File "<stdin>", line 1, in ?

ZeroDivisionError: integer division or modulo #Деление на нуль

>>> 4 + spam*3

Traceback (most recent call last):

File "<stdin>", line 1, in ?

NameError: spam

#Нет такого имени переменной

>>> '2' + 2

Traceback (most recent call last):

File "<stdin>", line 1, in ?

TypeError: illegal argument type for built-in operation#Смешение разных типов



Конечно, всё было бы отлично, если пользователя удовлетворяли подобные сообщения, я лично в этом сильно сомневаюсь. Кроме этого, исключения могут генерировать ся в любой момент программы, что может вызвать потерю данных. По всем этим причинам желательно предусматривать возможность перехватывать исключения. Для этого используется блок try, который исполняет операторы внутри блока, но если возбуждается исключение, то оператор try ищет обработчик исключения(прерывая исполнение блока кода внутри try), обозначаемый except имя_исключения . Если обработчик не найден, то он ищется в других блоках try, если он не найден, то возникает непредвиденное(unhandled) исключение, которое отображается, как в предыдущем примере. Блок try выполняется до конца при отсутствии исключительных ситуаций, блоки except при этом пропускаются:

>>> while 1:

... try:

... x = int(raw_input("Введите число ")) #Здесь может возникнуть исключение

... break #Если всё правильно, то выходим из бесконечного while

... except ValueError: #А вот здесь обрабатывается исключение неверного формата числа

... print ''Ой-ой. Неправильное число. Попробуйте снова..."



Оператор except может принимать несколько имён исключений, оформленных в скобках через запятую:

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

... pass

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

import string, sys



try:

f = open('myfile.txt')

s = f.readline()

i = int(string.strip(s))

except IOError, (errno, strerror):

print "I/O ошибка(%s): %s" % (errno, strerror)

except ValueError:

print "Не могу преобразовать это в целое."

except:

print "Неожиданная ошибка:", sys.exc_info()[0] #Имя последнего исключения

raise #Возбуждение данного исключения ещё раз(см. далее)

В блоке try имеется дополнительный оператор else, который выполняется, при отсутствии исключений в блоке try:

for arg in sys.argv[1:]:

try:

f = open(arg, 'r')

except IOError:

print 'не могу открыть', arg

else:

print arg, 'имеет длину в ', len(f.readlines()), ' строк'

f.close()

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

>>> def this_fails():

... x = 1/0

...

>>> try:

... this_fails() #Функция, вызывающая исключительную ситуацию

... except ZeroDivisionError, detail:

... print 'Возбуждено исключение:', detail

...

Возбуждено исключение: integer division or modulo



Программное возбуждение исключений.

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

>>> raise NameError, 'HiThere'

Traceback (most recent call last):

File "<stdin>", line 1, in ?

NameError: HiThere

raise без параметров часто используется в промежуточных обработчиках исключений:

>>> try:

... raise NameError, 'HiThere'

... except NameError:

... print 'Произошла ошибка!'

... raise

...

Произошла ошибка!

Traceback (most recent call last):

File "<stdin>", line 2, in ?

NameError: HiThere



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



Заключительные действия.

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

>>> try:

... raise KeyboardInterrupt

... finally:

... print 'Goodbye, world!'

...

Goodbye, world!

Traceback (most recent call last):

File "<stdin>", line 2, in ?

KeyboardInterrupt



__finally блок выполняется независимо от того произошло исключение или нет, если исключение произошло, то оно возбуждается ещё раз. Оператор try может иметь или блоки except или __finally, но не одновременно. Блок __finally выполняется даже во вложенных try, поэтому использование вложенных try(с except) и внешнего с __finally способно отловить и корректно обработать любые возникшие исключения.