Раніше про помилки згадувалося лише побіжно, але якщо ви пробували приклади, подані раніше, то напевно вже бачили деякі з них. Існують (принаймні) два окремі типи помилок: синтаксичні помилки та винятки.
Синтаксичні помилки
Синтаксичні помилки є можливо головною проблемою коли ви ще тільки вивчаєте мову Пітон:
>>> while True print 'Привіт, світе'
File "<stdin>", line 1, in ?
while True print 'Hello world'
^
SyntaxError: invalid syntax
Парсер повторює рядок, де було порушено правило, і показує
маленьку "стрілку", що вказує на місце, де помилку було вперше
помічено. Ця помилка була спричинена знаком (або принаймні помічена
там), що передує стрілці : у
цьому прикладі помилку знайдено перед ключовим словом print
, тому що перед ним було пропущено двокрапку
(":"). Також виводиться назва файла та номер рядка, щоб ви знали, де
саме відбулася помилка, якщо ввід було отримано зі скрипта.
Винятки
Навіть коли твердження чи вираз синтаксично правильне, його виконання може призвести до помилки. Помилки, що відбуваються під час виконання програми звуться винятками і вони не є безумовно фатальними: незабаром ви довідаєтеся, як їх можна фільтрувати у програмах, написаних на Пітоні. Якщо помилка не обробляється програмою, то тоді її результатом буде повідомлення на зразок тих, що показані нижче:
>>> 10 * (1/0)
Traceback (most recent call last):
File "<stdin>", line 1, in ?
ZeroDivisionError: integer 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: cannot concatenate 'str' and 'int' objects
Останній рядок повідомлення про помилку вказує на те, що ж саме
трапилося. Винятки належать до різних типів і назва типу виводиться як
частина повідомлення. У наведених вище прикладах типами помилок
є ZeroDivisionError
(поділ на нуль), NameError
(помилка назви)
та TypeError
(помилка типу).
Назва помилки, що виводиться і є назвою вбудованого винятку, що
відбувся. Це справедливо для всіх вбудованих винятків, але не
завжди - для винятків, заданих користувачем (хоча це і корисна
конвенція). Назви стандартних винятків - це вбудовані ідентифікатори (а
не
зарезервовані ключові слова).
Решта в цьому рядку - деталі, інтерпретація та значення яких залежить від типу помилки.
У попередній частині повідомлення йдеться про контекст, де відбулася помилка, у формі сліду стека. Загалом там подано лише рядки джерела; рядки, зчитані зі стандартного вводу не подаються.
Перелік вбудованих винятків та їхніх значень подається в Бібліотеці.
Фільтрування помилок
Можливо написати програми, здатні обробляти задані помилки.
Розгляньмо наступний приклад, де програма вимагає вводу
допоки не буде введено ціле число, але при цьому дозволяє користувачеві
перервати програму
(використовуючи Control-C
чи щось інше, що розуміє
операційна система); зауважте, що переривання, викликане користувачем,
супроводжується створенням винятку KeyboardInterrupt
.
>>> while True:
... try:
... x = int(raw_input("Введіть, будь-ласка, ціле число: "))
... break
... except ValueError:
... print "Недійсне число. Спробуйте знову..."
...
Твердження try
працює таким чином:
- Спочатку виконується блок, розташований між ключовими словами
try
таexcept
. - Якщо жодного винятку не відбулося, то твердження, розташовані за
except
пропускаються і таким чином закінчується виконання блокуtry
. - Якщо виняток трапився під час виконання одного з тверджень блоку
try
, то решта його тверджень пропускається.
Після цього, якщо тип помилки збігається з типом винятку, зазначеним після ключового слова except
, то
виконуються твердження блоку except
, після чого продовжується виконання коду, розташованого за твердженням
try
.
- Якщо виняток, що відбувся, не відповідає винятку, названому після ключового слова
except
, то він передається
зовнішнім твердженням try
. Якщо виняток так і не було відфільтровано, то він вважається _неопрацьованим _
винятком і виконання програми зупиняється із виведенням помилки, як показано вище.
Твердження try
може мати більш ніж одну
конструкцію except
для обробки різних
помилок, але не більше ніж одну з них буде виконано. Оброблено буде
лише помилки, що відбулися у відповідному блоці try
,
а не всередпні самих фільтрів.
Конструкція except
може опрацьовувати кілька
винятків одночасно; при цьому їх назви оточуються дужками:
... except (RuntimeError, TypeError, NameError):
... pass
Остання конструкція except
може не
вказувати назви винятків, що може використовуватися для обробки
будь-якої помилки. Цю рису слід використовувати з надзвичайною
обережністю, бо з її допомогою можна легко приховати справжні помилки у
програмі!
Вона також може служити для виводу повідомлення про помилку з повторним
підняттям помилки (що може використовуватися для її подальшої обробки в
середовищі, яке викликало даний код):
import sys
try:
f = open('myfile.txt')
s = f.readline()
i = int(s.strip())
except IOError, (errno, strerror):
print "Помилка вводу/виводу (%s): %s" % (errno, strerror)
except ValueError:
print "Неможливо конвертувати дані в ціле число."
except:
print "Несподівана помилка:", sys.exc_info()[0]
raise
Твердження try
... except
має необов'язкову конструкцію else ("інакше"), яка, якщо
присутня, повинна закривати всі конструкції except
.
Це корисно для створення коду, що має виконатися в разі, якщо не
відбулося жодного винятку. Наприклад::
for arg in sys.argv[1:]:
try:
f = open(arg, 'r')
except IOError:
print 'неможливо відкрити', arg
else:
print arg, 'має', len(f.readlines()), 'рядків'
f.close()
Вживання блоку else
- краще, ніж створення
додаткового коду всередині блоку try
, тому що
дозволяє уникати випадкової обробки винятку, який не повинен був
підніматися
всередині коду, захищеного твердженням try
... except
.
Виняток може мати певну величину, що асоціюється з ним. Ця величина також відома як аргумент винякту. Присутність і тип цього аргументу залежать від типу винятку.
Блок except
може визначати змінну (або
список) після
назви винятку. Ця змінна прив'язується до
реалізації винятку, чиї аргументи зберігаються у instance.args
.
Для зручності, реалізація винятку визначає __getitem__
та __str__
, а отже вивід та доступ
до аргументів може здійснюватися напряму без посилання на .args
.
>>> try:
... raise Exception('spam', 'eggs')
... except Exception, inst:
... print type(inst) # реалізація винятку
... print inst.args # аргументи збережено в .args
... print inst # __str__ дозволяє виводити аргументи напряму
... x, y = inst # __getitem__ дозволяє розпаковувати аргументи напряму
... print 'x =', x
... print 'y =', y
...
<type 'instance'>
('spam', 'eggs')
('spam', 'eggs')
x = spam
y = eggs
Якщо виняток має аргумент, то він виводиться як остання частина ("деталь") повідомлення для необроблених помилок.
Фільтри винятків обробляють не лише ті помилки,
що
відбулися безпосередньо у блоці try
, але і
ті, що виникають всередині функцій, які викликаються (навіть
опосередковано) із блоку try
. Наприклад:
>>> def this_fails():
... x = 1/0
...
>>> try:
... this_fails()
... except ZeroDivisionError, detail:
... print 'Відбулася помилка при виконанні:', detail
...
Відбулася помилка при виконанні: integer division or modulo
Створення винятків
Твердження raise
дозволяє програмісту
задавати винятки.
Наприклад:
>>> raise NameError, 'ПривітВам'
Traceback (most recent call last):
File "<stdin>", line 1, in ?
NameError: ПривітВам
Перший аргумент для raise
називає
виняток, що створюється. Необов'язковий другий аргумент є аргументом
самого винятку.
Якщо потрібно лише визначити, чи було піднято помилку, без подальшої
її обробки, простіша форма твердження raise
дозволяє заново підняти помилку:
>>> try:
... raise NameError, 'ПривітВам'
... except NameError:
... print 'Тут промайнув виняток!'
... raise
...
Тут промайнув виняток!
Traceback (most recent call last):
File "<stdin>", line 2, in ?
NameError: ПривітВам
Винятки, визначені користувачем
Програми можуть визначати нові винятки шляхом створення нового класу
винятку. Загалом, винятки повинні походити із класу 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, e:
... print 'Відбувся мій виняток, величина:', e.value
...
Відбувся мій виняток, величина: 4
>>> raise MyError, 'oops!'
Traceback (most recent call last):
File "<stdin>", line 1, in ?
__main__.MyError: 'oops!'
Класи винятків (як і звичайні класи) можуть виконуватимуть будь-що, але загалом вони створюються максимально простими і мають обмежену кількість атрибутів, для надання інформації про помилку, яка може використовуватися фільтрами винятків. При створенні модуля, що може відкинути кілька окремих помилок, традиційно створюється базовий клас для всіх винятків, визначених у цьому модулі, а також підклас для створення специфічних класів винятків для окремих помилок:
class Error(Exception):
"""Базовий клас для винятків цього модуля."""
pass
class InputError(Error):
"""Винятки для помилок вводу.
Атрибути:
expression -- вираз вводу, де відбулася помилка
message) -- повідомлення про помилку
"""
def __init__(self, expression, message):
self.expression = expression
self.message = message
class TransitionError(Error):
"""Відкидається при недозволеній зміні стану.
Атрибути:
previous -- початковий стан переходу
next -- пропонований наступний стан
message -- пояснення, чому такий перехід не дозволяється
"""
def __init__(self, previous, next, message):
self.previous = previous
self.next = next
self.message = message
Більшість винятків мають назви, що закінчуються словом "Error", подібно до назв стандартних винятків.
Багато стандартних модулів визначають свої власні винятки для повідомлень про помилки, що можуть відбутися у визначених ними функціях. Докладніша інформація про класи подана в наступному розділі.
Визначення очищувальних дій
Твердження try
ще один необов'язковий
блок, який визначає різні "очищувальні" дії, що мають виконатися за
будь-яких обставин. Наприклад:
>>> try:
... raise KeyboardInterrupt
... finally:
... print 'Прощай, світе!'
...
Прощай, світе!
Traceback (most recent call last):
File "<stdin>", line 2, in ?
KeyboardInterrupt
Цей заключний блок
виконується незалежно від того, чи відбувся виняток у блоці try
. Якщо
відбувся виняток, то він заново відкинеться після виконання блоку finally
. Фінальний блок
також виконується при віході з блоку try
за допомогою тверджень break
чи return
.
Код у заключному блоці корисний для звільнення зовнішніх ресурсів (таких як файли чи зв'язки з мережею), незалежно від того, чи використання цих ресурсів було успішним, чи ні.
Твердження try
повинно мати або один чи
більше
блоків except
, або один блок finally
,
але не
обидва разом.