Изузеци

Програмери морају увек имати на уму могуће грешке које се могу појавити у њиховим програмима. Примера има на претек: функција можда неће примити аргументе које је пројектована да прихвати, можда недостају неопходни ресурси или се може изгубити веза преко мреже. Приликом пројектовања програма, морају се предвидети изузетне околности које могу настати и морају се предузети одговарајуће мере за њихово решавање.

Не постоји јединствен исправан приступ руковођењу грешкама у програму. Програми пројектовани да пружају неке трајне услуге попут веб сервера треба да буду робусни према грешкама, бележећи их за касније разматрање, али настављајући да сервисирају нове захтеве што је дуже могуће. С друге стране, Пајтонов интерпретатор обрађује грешке прекидајући извршавање одмах и исписујући поруку о грешци, тако да програмери могу да реше проблеме чим се појаве. У сваком случају, програмери морају свесно да одлуче како ће њихови програми да реагују на неке ванредне услове.

Изузеци, што је и тема овог одељка, пружају општи механизам за додавање логике за руковање и руковођење грешкама у програмима. Подизање изузетка је техника за прекидање нормалног тока извршавања у програму, сигнализирање да се појавила нека ванредна околност и враћање директно у затворени део програма који је одређен да реагује на ту околност. Пајтонов интерпретатор покреће изузетак сваки пут када открије грешку у изразу или наредби. Корисници такође могу подићи изузетке помоћу raise и assert наредби.

Подизање изузетака

Изузетак је инстанца објекта са класом која наслеђује, било директно или индиректно, класу BaseException. Наредба assert представљена у првом поглављу доводи до изузетка класе AssertionError. Уопштено, било која инстанца изузетка може се подићи raise наредбом. Општи облик наредбе за подизање изузетка описан је у Пајтон документацији. Најчешћа употреба raise прави изузетак и подиже га.

>>> raise Exception('Десила се грешка!')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
Exception: Десила се грешка!

Када се подигне изузетак, не извршавају се даљи изрази и наредбе у тренутном блоку кода. Изузев ако се изузетак не обрађује (описано у наставку), интерпретатор ће се директно вратити у интерактивну петљу читања-вредновања-исписа (енг. read-eval-print loop или REPL) или ће се у потпуности завршити ако је Пајтон покренут са аргументом датотеке. Поред тога, интерпретатор ће исписати такозвани stack backtrace, који је структурирани блок текста који описује угнежђени скуп активних позива функције у грани извршења у којој је подигнут изузетак. У примеру изнад, име датотеке <stdin> указује на то да је изузетак покренуо корисник у интерактивној сеанси, а не из кода у датотеци.

Обрада изузетака

Изузетак се може обрадити обухватајућом try наредбом. Наредба try састоји се из више клаузула; прва започиње са try, док остале почињу са except:

try:
    <try пакет>
except <класа изузетка> as <име>:
    <except пакет>
...

Тело <try пакет> се увек извршава одмах после try наредбе. Пакети except клаузула извршавају се само када се подигне одговарајући изузетак током извршавања <try пакет>. Свака except клаузула специфицира одређену класу изузетка коју треба обрадити и којом треба руководити. На пример, ако је <класа изузетка> заправо AssertionError, тада ће било која инстанца класе која наслеђује AssertionError која се подигне током извршавања <try пакет> бити обрађена следећим <except пакет>-у. Унутар <except пакет>-а, идентификатор <име> је везан за објекат изузетка који је подигнут, али ово везивање не постоји ван <except пакет>-а.

На пример, можемо се обрадити ZeroDivisionError изузетак користећи се try наредбом која везује назив x на 0 када се изузетак подигне.

>>> try:
...     x = 1/0
... except ZeroDivisionError as e:
...     print('обрађује се', type(e))
...     x = 0
обрађује се <class 'ZeroDivisionError'>
>>> x
0

Наредба try ће руководити изузецима који се јављају у телу функције која се примењује (било директно или индиректно) у оквиру <try пакет>-а. Када се изузетак подигне, контрола прелази директно на тело <except пакет>-а најновије try наредбе која обрађује ту врсту изузетака.

>>> def инвертуј(x):
...     result = 1/x  # Подиже ZeroDivisionError ако је x једнако 0
...     print('Никада се не штампа ако је x једнако 0')
...     return result
>>> def инвертујБезбедно(x):
...     try:
...         return инвертуј(x)
...     except ZeroDivisionError as e:
...         return str(e)
>>> инвертујБезбедно(2)
Никада се не штампа ако је x једнако 0
0.5
>>> инвертујБезбедно(0)
'division by zero'

Овај пример илуструје да се print наредба унутар инвертуј никада не извршава, већ се уместо тога контрола пребацује у пакет except клаузула у инвертујБезбедно. Претварањем ZeroDivisionError e у ниску даје човеку разумљиву текстуалну вредност која се враћа од стране инвертујБезбедно, то јест: 'division by zero'.

Објекти изузетака

Сами објекти изузетака могу имати атрибуте, као што је порука о грешци наведена у assert наредби и информација о томе где је у току извршавања подигнут изузетак. Кориснички дефинисане класе изузетака могу имати и додатне атрибуте.

У првом поглављу имлементирана је такозвана Њутнова метода за проналажење нула произвољних функција. Следећи пример дефинише класу изузетка која враћа најбољу претпоставку, то јест нагађање октривено током итеративног поступка побољшања нагађања кад год се догоди ValueError грешка. Грешка у математичком домену (тип ValueError грешке) настаје када се рецимо квадратни корен sqrt примени на негативан број. Овај изузетак се обрађује подизањем IterImproveError који као атрибут чува најновије нагађање из Њутнове методе.

Најпре, дефинише се нова класа која наслеђује Exception.

>>> class IterImproveError(Exception):
...     def __init__(self, последњеНагађање):
...         self.последњеНагађање = последњеНагађање

Затим се дефинише верзија побољшај, генеричка функција за алгоритме итеративног побољшања. Ова верзија обрађује било који ValueError изузетак тако што подиже IterImproveError који чува најсвежије нагађање. Као и раније, побољшај узима као аргументе две функције, од којих свака прима по један нумерички аргумент. Функција ажурирај враћа нова нагађања, док функција готово даје логичку вредност која указује да је побољшање конвергирало ка тачној вредности.

>>> def побољшај(update, готово, нагађање=1, max_updates=1000):
...     k = 0
...     try:
...         while not готово(нагађање) and k < max_updates:
...             нагађање = update(нагађање)
...             k = k + 1
...         return нагађање
...     except ValueError:
...         raise IterImproveError(нагађање)

Коначно, дефинише се пронађиНулу, који враћа резултат побољшај примењен на Њутнову функцију ажурирања коју враћа њутновоАжурирање, која је дефинисана још у прво поглављу, а овде поновљена у идентичном облику чисто ради комплетности. Ова верзија пронађиНулу функције обрађује IterImproveError враћањем последњег нагађања.

>>> def њутновоАжурирање(f, df):
...     def ажурирај(x):
...         return x - f(x) / df(x)
...     return ажурирај
>>> def пронађиНулу(f, df, нагађање=1):
...     def готово(x):
...         return f(x) == 0
...     try:
...         return побољшај(њутновоАжурирање(f, df), готово, нагађање)
...     except IterImproveError as e:
...         return e.последњеНагађање

Размислити о примени пронађиНулу како би се пронашла нула функције \(2x^2+\sqrt{x}\) чији је извод једнак \(4x+1/2\sqrt{x}\). Ова функција има нулу у нули, али вредновање на било којм негативном броју подићи ће ValueError. Имплементација Њутнове методе из првог поглавља би подигла грешку и не би вратила било какву апроксимацију нуле функције. Ова пак ревидирана имплементација враћа последње нагађање пронађено пре грешке.

>>> from math import sqrt
>>> пронађиНулу(lambda x: 2*x*x + sqrt(x), lambda x: 4*x + 1/(2*sqrt(x)))
-0.030214676328644885

Иако је ова апроксимација још увек далеко од тачног одговора што је 0, неким апликацијама више одговара ова груба апроксимација него ValueError.

Изузеци су још једна техника која помаже програмерима да раздвоје бриге и проблеме унутар програма на модуларне делове. У овом примеру, Пајтонов механизам за изузетке је дозволио да се раздвоји логика за итеративно побољшање, која се чини непромењеном у пакету try клаузуле, од логике за обрадом и руковођењем грешкама, која се појављује у except клаузулама. Такође ће бити приказано да су изузеци корисна одлика приликом имплементације интерпретатора у Пајтону.