Изузеци¶
Програмери морају увек имати на уму могуће грешке које се могу појавити у њиховим програмима. Примера има на претек: функција можда неће примити аргументе које је пројектована да прихвати, можда недостају неопходни ресурси или се може изгубити веза преко мреже. Приликом пројектовања програма, морају се предвидети изузетне околности које могу настати и морају се предузети одговарајуће мере за њихово решавање.
Не постоји јединствен исправан приступ руковођењу грешкама у програму. Програми пројектовани да пружају неке трајне услуге попут веб сервера треба да буду робусни према грешкама, бележећи их за касније разматрање, али настављајући да сервисирају нове захтеве што је дуже могуће. С друге стране, Пајтонов интерпретатор обрађује грешке прекидајући извршавање одмах и исписујући поруку о грешци, тако да програмери могу да реше проблеме чим се појаве. У сваком случају, програмери морају свесно да одлуче како ће њихови програми да реагују на неке ванредне услове.
Изузеци, што је и тема овог одељка, пружају општи механизам за додавање логике за руковање и руковођење грешкама у програмима. Подизање изузетка је техника за прекидање нормалног тока извршавања у програму, сигнализирање да се појавила нека ванредна околност и враћање директно у затворени део програма који је одређен да реагује на ту околност. Пајтонов интерпретатор покреће изузетак сваки пут када открије грешку у изразу или наредби. Корисници такође могу подићи изузетке помоћу 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
клаузулама. Такође ће бити приказано да су изузеци корисна одлика приликом имплементације интерпретатора у Пајтону.