.. _exceptions: ======= Изузеци ======= Програмери морају увек имати на уму могуће грешке које се могу појавити у њиховим програмима. Примера има на претек: функција можда неће примити аргументе које је пројектована да прихвати, можда недостају неопходни ресурси или се може изгубити веза преко мреже. Приликом пројектовања програма, морају се предвидети изузетне околности које могу настати и морају се предузети одговарајуће мере за њихово решавање. Не постоји јединствен исправан приступ руковођењу грешкама у програму. Програми пројектовани да пружају неке трајне услуге попут веб сервера треба да буду робусни према грешкама, бележећи их за касније разматрање, али настављајући да сервисирају нове захтеве што је дуже могуће. С друге стране, Пајтонов интерпретатор обрађује грешке прекидајући извршавање одмах и исписујући поруку о грешци, тако да програмери могу да реше проблеме чим се појаве. У сваком случају, програмери морају свесно да одлуче како ће њихови програми да реагују на неке ванредне услове. *Изузеци*, што је и тема овог одељка, пружају општи механизам за додавање логике за руковање и руковођење грешкама у програмима. Подизање изузетка је техника за прекидање нормалног тока извршавања у програму, сигнализирање да се појавила нека ванредна околност и враћање директно у затворени део програма који је одређен да реагује на ту околност. Пајтонов интерпретатор покреће изузетак сваки пут када открије грешку у изразу или наредби. Корисници такође могу подићи изузетке помоћу ``raise`` и ``assert`` наредби. .. _raisingExceptions: Подизање изузетака ------------------ Изузетак је инстанца објекта са класом која наслеђује, било директно или индиректно, класу ``BaseException``. Наредба ``assert`` представљена у првом поглављу доводи до изузетка класе ``AssertionError``. Уопштено, било која инстанца изузетка може се подићи ``raise`` наредбом. Општи облик наредбе за подизање изузетка описан је у `Пајтон документацији `_. Најчешћа употреба ``raise`` прави изузетак и подиже га. >>> raise Exception('Десила се грешка!') Traceback (most recent call last): File "", line 1, in Exception: Десила се грешка! Када се подигне изузетак, не извршавају се даљи изрази и наредбе у тренутном блоку кода. Изузев ако се изузетак не *обрађује* (описано у наставку), интерпретатор ће се директно вратити у интерактивну петљу читања-вредновања-исписа (енг. *read-eval-print loop* или `REPL `_) или ће се у потпуности завршити ако је Пајтон покренут са аргументом датотеке. Поред тога, интерпретатор ће исписати такозвани *stack backtrace*, који је структурирани блок текста који описује угнежђени скуп активних позива функције у грани извршења у којој је подигнут изузетак. У примеру изнад, име датотеке ```` указује на то да је изузетак покренуо корисник у интерактивној сеанси, а не из кода у датотеци. .. _handlingExceptions: Обрада изузетака ---------------- Изузетак се може обрадити обухватајућом ``try`` наредбом. Наредба ``try`` састоји се из више клаузула; прва започиње са ``try``, док остале почињу са ``except``:: try: except <класа изузетка> as <име>: ... Тело ```` се увек извршава одмах после ``try`` наредбе. Пакети ``except`` клаузула извршавају се само када се подигне одговарајући изузетак током извршавања ````. Свака ``except`` клаузула специфицира одређену класу изузетка коју треба обрадити и којом треба руководити. На пример, ако је ``<класа изузетка>`` заправо ``AssertionError``, тада ће било која инстанца класе која наслеђује ``AssertionError`` која се подигне током извршавања ```` бити обрађена следећим ````-у. Унутар ````-а, идентификатор ``<име>`` је везан за објекат изузетка који је подигнут, али ово везивање не постоји ван ````-а. На пример, можемо се обрадити ``ZeroDivisionError`` изузетак користећи се ``try`` наредбом која везује назив ``x`` на 0 када се изузетак подигне. >>> try: ... x = 1/0 ... except ZeroDivisionError as e: ... print('обрађује се', type(e)) ... x = 0 обрађује се >>> x 0 Наредба ``try`` ће руководити изузецима који се јављају у телу функције која се примењује (било директно или индиректно) у оквиру ````-а. Када се изузетак подигне, контрола прелази директно на тело ````-а најновије ``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'``. .. _exceptionObjects: Објекти изузетака ----------------- Сами објекти изузетака могу имати атрибуте, као што је порука о грешци наведена у ``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.последњеНагађање Размислити о примени ``пронађиНулу`` како би се пронашла нула функције :math:`2x^2+\sqrt{x}` чији је извод једнак :math:`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`` клаузулама. Такође ће бити приказано да су изузеци корисна одлика приликом имплементације интерпретатора у Пајтону.