Підручник мови Python/Помилки та винятки

Помилки та винятки

ред.

Раніше про помилки згадувалося лише побіжно, але якщо ви пробували виконувати приклади, подані раніше, то напевно вже бачили деякі з них. Існують два окремі типи помилок: синтаксичні помилки та винятки.

Синтаксичні помилки

ред.

Синтаксичні помилки є можливо найбільшою проблемою коли ви починаєте вивчати мову Пайтон:

>>> 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 'Відбувся мій виняток, {{error|{{error|велич}}}}ина:', e.value
... 
Відбувся мій виняток, {{error|{{error|велич}}}}ина: 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, але не обидва разом.

Механізм створення класів у Пайтоні додає класи до мови з мінімальним використанням нового синтаксису та семантики. Він є сумішшю рис, що існують у мовах C++ та Modula-3. Як і модулі, класи у Пайтоні не створюють абсолютного бар'єру між їхнім визначенням та користувачем, а ґрунтуються на ввічливому принципі "невтручання у визначення". Найважливіші риси класів зберігають повну міць, але: механізм успадкування класів дозволяє численні базові класи, похідний клас може переписати будь-який метод свого базового класу (чи класів), метод може викликати однойменний метод базового класу. Об'єкти можуть мати будь-яку кількість приватних даних.

Якщо послуговуватися термінологією мови C++, то усі члени класу (включно з членами даних) — публічні, а всі функції класу — віртуальні. Спеціальних конструкторів чи деструкторів не існує. Подібно до мови Modula-3 у Пайтоні не існує скорочень для посилання на члени об'єкта із його методів: функція методу задається із явним першим аргументом, що репрезентує об'єкт, який неявно передається при виклику цієї функції. Подібно до мови Smalltalk самі класи також є об'єктами, хоча і в ширшому сенсі: у мові Пайтон всі типи даних — об'єкти. Це визначає семантику імпортування та перейменування. На відміну від C++ та Modula-3 вбудовані типи можуть використовуватися як базові класи для створених користувачем розширень. Також, подібно до C++, але на відміну від Modula-3, більшість вбудованих операторів, що мають спеціальний синтаксис (арифметичні оператори, індексатори тощо) можуть перевизначатися в реалізаціях класів.