Дефинисање нових функција

Идентификовани су у Пајтону неки од елемената који се морају појавити у сваком моћном програмском језику:

  1. Бројеви и аритметичке операције су примитивне уграђене вредности података и функције, респективно.

  2. Угнежђене примене функција пружају средство за комбиновање операција.

  3. Повезивање имена на вредности пружа ограничен начин апстракције.

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

Започећемо испитивањем како изразити идеју квадрирања. Може се рећи, „да би се нешто квадрирало, поможити га са самим собом”. Ово се у Пајтону изражава као

>>> from operator import add, mul
>>> def квадрат(x):
...     return mul(x, x)

чиме је дефинисана нова функција којој је дато име квадрат. Ова кориснички дефинисана функција није уграђена у интерпретатор. Она представља сложену операцију множења нечега са самим собом. У овој дефиницији x се назива формални параметар и даје име ономе што треба помножити. Дефиниција ствара ову корисничку функцију и придружује јој име квадрат.

Како дефинисати функцију? Дефиниције функција се састоје из def наредбе која указује на <име> и зарезима раздвојен низ имена <формални параметри> праћен return наредбом која се зове тело функције и која одређује <повратни израз> функције, израз који се вреднује сваки пут када је функција позвана, односно примењена:

def <име>(<формални параметри>):
    return <повратни израз>

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

Пошто је функција квадрат дефинисана, може се применити позивним изразом:

>>> квадрат(21)
441
>>> квадрат(add(2, 5))
49
>>> квадрат(квадрат(3))
81

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

>>> def збирКвадрата(x, y):
...     return add(квадрат(x), квадрат(y))
>>> збирКвадрата(3, 4)
25

Кориснички дефинисане функције се користе на идентичан начин као и уграђене функције. И заиста, из дефиниције функције збирКвадрата не може се рећи да ли је функција квадрат уграђена у интерпретатор, увезена из модула или пак дефинисана од стране корисника.

Обе, и def наредбе и наредбе доделе везују имена на вредности, а сва постојећа везивања су изгубљена. На пример, g се, у наставку, најпре односи на функцију без аргумената, затим на број, а потом и на различиту функцију два аргумента.

>>> def g():
...     return 1
>>> g()
1
>>> g = 2
>>> g
2
>>> def g(h, i):
...     return h + i
>>> g(1, 2)
3

Окружења

До сада представљен подскуп Пајтона је довољно сложен да значења програма нису више тако једноставна и очигледна. Шта ако формални параметар има исто име као и уграђена функција? Могу ли две функције делити исто име без забуне? Да би се разрешила оваква питања морају се детаљније описати окружења.

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

Један овакав дијаграм окружења приказује везе тренутног окружења заједно са вредностима на које су имена повезана. Дијаграми окружења у овом тексту су интерактивни, односно могуће је ићи кроз линије краћих програма с леве стране да би се видело како се стање окружења развија с десне стране. Може се чак и кликнути на „Edit code in Online Python Tutor” везу да се пример учита у Пајтон тутор, алат који је направио Филип Гуо за генерисање ових дијаграма окружења. Охрабрују се читаоци да унесу примере и простудирају тако добијене дијаграме окружења.

Функције се такође приказују у дијаграмима окружења. Наредба import везује име на уграђену функцију.

Наредба def повезује име на кориснички дефинисану функцију створену путем дефиниције. Резултујуће окружење након увоза mul и квадрат дефиниције је приказано у наставку.

Свака функција је ред који почиње са func, праћен именом функције и формалним параметрима. Уграђене функције као што су mul немају имена формалних параметара па су зато ... искоришћене уместо истих.

Име функције се исписује два пута, једном у окружењу и поново као део саме функције. Име које се појављује у самој функцији назива се својствено име. Име у оквиру је везано име. Постоји разлика између њих: различита имена могу се односити на исту функцију, али сама та функција има само једно својствено име.

Име везано на функцију у оквиру је оно које се користи током вредновања. Својствено име функције не игра улогу у вредновању. Може се видети у доњем примеру да једном када се назив max веже на вредност 3, не може се више користити као функција.

Порука о грешци TypeError: 'int' object is not callable даје обавештење да име max (тренутно повезано на број 3) представља целобројну вредност, а не функцију. Стога, не може се користити као оператор у позивном изразу.

Потписи функција

Функције се разликују по броју аргумената које узимају. Да се испрате ови захтеви свака функција се црта на начин који приказује име функције и њене формалне параметре. Кориснички дефинисана функција квадрат прима само x и пружање мање или више аргумената изазваће грешку. Опис формалних параметара функције назива се потпис функције.

Функција max може примити произвољан број аргумената. Приказује као max(...). Без обзира на број аргумената који примају, све уграђене функције биће приказане као <име>(...) зато што ове примитивне функције никада и нису експлицитно дефинисане.

Позивање кориснички дефинисаних функција

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

Примена кориснички дефинисане функције уноси други локални оквир који је само доступан тој функцији. Да се примени кориснички дефинисана функција не неке аргументе:

  1. Повезати аргументе с именима формалних параметара функције у новом локалном оквиру.

  2. Извршити тело функције у окружењу које почиње овим новонаправљеним оквиром.

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

Како би се један пример детаљно илустровао, треба исцртати неколико корака у дијаграму окружења једног примера. Након извршења прве наредбе увоза, само је име mul повезано у глобалном оквиру.

Најпре, наредба дефинисања функције квадрат је извршена. Треба приметити да се целокупна def наредба обрађује у једном кораку. Тело функције се не извршава приликом дефиниције функције, све док функција не буде позвана.

Следеће, функција квадрат је позвана са аргументом -2 па се ствара нови оквир с формалним параметром x повезаним на вредност -2.

Затим, име x се тражи у тренутном окружењу које се састоји из два оквира. У овом случају x се вреднује у -2 и тако функција квадрат враћа 4.

Повратна вредност у оквиру квадрат() функције није везивање имена, већ уместо тога означава вредност коју враћа позив функције која је створила тај оквир.

Чак и у овом једноставном примеру, два различита окружења су коришћена. Израз квадрат(-2) из највишег нивоа се вреднује у глобалном окружењу док је повратни израз mul(x, x) вреднован у окружењу направљеном позивом квадрат функције. Оба имена и x и mul су повезана у овом окружењу, али у различитим оквирима.

Редослед оквира у окружењу утиче на повратну вредност услед тражења имена у изразу. Претходно је наведено да се име вреднује у вредност која је придружена том имену у тренутном окружењу. У наставку ће се то још прецизирати.

Вредновање имена

Име се вреднује у вредност која је повезана на то име у најранијем оквиру тренутног окружења у коме је то име пронађено.

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

Пример: Позивање кориснички дефинисане функције

Размотримо изнова исте две једноставне дефиниције функција које илуструју поступак вредновања позивног израза кориснички дефинисане функције.

Пајтон најпре вреднује име збирКвадрата које је везано на кориснички дефинисану функцију у глобалном оквиру. Примитивни бројевни изрази 5 и 12 вреднују се у бројеве које представљају.

Даље, Пајтон примењује збирКвадрата што уводи и локални оквир који повезује x на 5 и y на 12.

Тело функције збирКвадрата садржи овај позивни израз:

-     add     (  квадрат(x)  ,  квадрат(y)  )
-   ________     __________     __________
-   оператор      операнд0       операнд1

Сва три подизраза се вреднују у тренутном окружењу које почиње оквиром означеним са збирКвадрата(). Подизраз оператора add је име које се налази у глобалном оквиру и везано је на уграђену функцију за сабирање. Два подизраза за операнде морају бити вредновани по реду пре него што се примени функција сабирања. Оба операнда се вреднују у тренутном окружењу које почиње оквиром са ознаком збирКвадрата.

У операнд0, име квадрат је кориснички дефинисана функција у глобалном оквиру, док је x име броја 5 у локалном оквиру. Пајтон примењује функцију квадрат на 5 уводећи још један локални оквир који повезује x на 5.

Користећи ово окружење, израз mul(x, x) се вреднује у 25.

Поступак вредновања сада прелази на операнд1 за који y именује број 12. Пајтон вреднује тело функције квадрат поново, овога пута уводећи још један локални оквир који повезује x на 12. Стога, операнд1 се вреднује у 144.

Коначно, примењујући сабирање на аргументима 25 и 144 доноси крајњу повратну вредност за збирКвадрата: 169.

Овај пример илуструје много темељних идеја које су до сада развијене. Имена су повезана на вредности које су расподељене преко више независних локалних оквира упоредо са јединственим глобалним оквиром који садржи дељена имена. Нови локални оквир се уводи сваки пут када је функција позвана, чак и ако се иста функција позива двапут.

Сва ова машинерија постоји како би осигурала да се имена разлучују у тачне вредности и у тачним временским тренуцима током извршавања програма. Овај пример илуструје зашто модел захтева сву сложеност која је уведена. Сва три локална оквира садрже везу имена x, али је то име везано на различите вредности у различитим оквирима. Локални оквири су ти који држе ова имена раздвојена.

Локална имена

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

>>> def квадрат(x):
...     return mul(x, x)
>>> def квадрат(y):
...     return mul(y, y)

Овај принцип – да је значење функције независно од имена параметара одабраних од стране њеног аутора - има важне последице на програмске језике. Најпростија последица јесте да имена параметара функције морају остати локални за тело те функције.

Уколико параметри не би били локални за тело њима одговарајуће функције, тада би параметар x у функцији квадрат могао бити побркан са параметром x у збирКвадрата. Критично је да ово није случај: везе имена x у различитим локалним оквирима су неповезане. Модел израчунавања је пажљиво пројектован да осигура ову независност.

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

Избор имена

Заменљивост имена не подразумева да су имена формалних параметара потпуно неважна. Напротив, добро изабрана имена функције и параметара су кључна за читљивост и људску интерпретацију дефиниција функција!

Следеће смернице су преузете из стилских путоказа за Пајтон код који служи као водич за све (нејеретички и неодметнички настројене) Пајтон програмере. Заједнички скуп правила олакшава комуникацију међу члановима развојне заједнице. Као нуспојава праћења ових договора и споразума и сопствени код постаје све више и интерно конзистентан.

  1. Имена функција се пишу малим словима при чему су речи раздвојене подвлакама. Охрабрују се описна имена.

  2. Имена функција обично евоцирају операције примењене над аргументима од стране интерпретатора (нпр. print, add, квадрат) или име резултата (нпр. max, abs, sum).

  3. Имена параметара се пишу малим словима при чему су речи раздвојене подвлакама иако су пожељна имена која се састоје из само једне речи.

  4. Имена параметара требало би да евоцирају на њихову улогу у функцији, а не само дозвољену врсту аргумента.

  5. Једнословна имена параметара су прихватљива када је им је улога очигледна, али треба избегавати мало латинично слово l, велика латинична слова O или I како би се избегла збрка са цифрама.

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

Функције као апстракције

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

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

Тако, узимајући у обзир само вредности које враћају, следеће две функције за квадрирање броја су међусобно неразлучиве. Обе узимају бројевни аргумент и производе квадрат тог броја као вредност.

>>> def квадрат(x):
...     return mul(x, x)
>>> def квадрат(x):
...     return mul(x, x-1) + x

Другим речима, дефиниције функција треба да су у стању да потисну појединости. Корисници функције можда нису сами писали те функције већ су их добили од другог програмера као „црну кутију”. Нема потребе да програмер зна како је функција имлементирана да би је користио. Сама Пајтон библиотека има ову особину. Многи програмери користе функције које су тамо дефинисане, али мали број њих икада прегледа њихову имплементацију.

Аспекти функционалне апстракције

Да се савлада и усаврши коришћење функционалне апстракције, често је корисно размотрити три њена основна атрибута. Такозвани домен функције је скуп аргумената које узима. С друге стране, опсег функције је скуп свих вредности које функција може да врати. Коначно, намера функције је веза између улаза и израчунатих излаза (као и потенцијалних бочних ефеката које може имати). Разумевање функционалних апстракција кроз њихов домен, опсег и намеру је од пресудног значаја за њихову исправну употребу у сложеним програмима.

На пример, свака функција квадрат коју користимо да имплементирамо збирКвадрата треба имати следеће особине:

  • Домен је било који реалан број.

  • Опсег је ма који ненегативан реалан број.

  • Намера је да је излаз једнак квадрату улаза.

Ове особине не указују на то како се намера извршава, односно та појединост је апстракована.

Оператори

Математички оператори (као што су + и -) обезбедили су прве примере методе комбиновања, али још увек нису дефинисани поступци вредновања израза који садрже ове операторе.

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

>>> 2 + 3
5

једноставно сматрати да је то скраћени облик за

>>> add(2, 3)
5

Инфиксни запис се може угнежђивати, баш као и позивни изрази. Пајтон примењује уобичајена математичка правила за редослед и приоритет оператора која налажу како интерпретирати сложене изразе са више оператора. Наиме,

>>> 2 + 3 * 4 + 5
19

се вреднује тако да даје идентичан резултат као и

>>> add(add(2, mul(3, 4)), 5)
19

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

>>> (2 + 3) * (4 + 5)
45

се вреднује у исти резултат као

>>> mul(add(2, 3), add(4, 5))
45

Када је у питању дељење, Пајтон обезбеђује два инфиксна оператора: / и //. Први представља обично дељење с тим што је количник увек реалан број, односно децимална вредност, чак и у случају да је дељеник дељив делиоцем:

>>> 5 / 4
1.25
>>> 8 / 4
2.0

Оператор //, с друге стране, заокружује резултат дељења на целобројну вредност и представља такозвано целобројно дељење:

>>> 5 // 4
1
>>> -5 // 4
-2

Ова два оператора су скраћенице за truediv и floordiv функције.

>>> from operator import truediv, floordiv
>>> truediv(5, 4)
1.25
>>> floordiv(5, 4)
1

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