Управљање

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

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

Наредбе

У претходним одељцима првенствено је разматрано како вредновати изразе. Међутим, до сада су већ виђене три врсте наредби: наредба доделе, као и def и return наредбе. Ове наредбе саме по себи нису изрази, иако све оне садрже изразе као свој саставни део.

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

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

Размотрити, на пример,

>>> def квадрат(x):
...     mul(x, x)   # Пажња! Овај позив функције не враћа вредност.

Иако је овај пример исправан, вероватно је његова намера била нешто другачија. Тело функције се састоји од једног израза. Израз сам по себи јесте исправна наредба, али је последица наредбе та да је mul функција позвана, а њен резултат одбачен. Уколико се жели урадити нешто с резултатом израза, потребно је сачувати га наредбом доделе или га вратити return наредбом:

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

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

>>> def штампајКвадрат(x):
...     print(квадрат(x))

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

Сложене наредбе

Углавном, Пајтонов изворни код представља низ наредби. Проста наредба јесте један ред који се не завршава двотачком. Сложена наредба се тако назива због тога што је састављена из других наредби (простих и сложених). Сложене наредбе обично се протежу на више редова и почињу заглављем у једном реду са двотачком као завршетком, које идентификује врсту наредбе. Заглавље заједно са увученим пакетом наредби које следе се назива клаузула. Сложена наредба се састоји из једне или више клаузула:

<заглавље>:
    <наредба>
    <наредба>
    ...
<раздвајајуће заглавље>:
    <наредба>
    <наредба>
    ...
...

Наредбе које су већ уведене могу се разумети на следећи начин.

  • Изрази, return наредбе и наредбе доделе су просте наредбе.

  • Наредба, def је сложена наредба. Пакет увучених наредби које следе def заглавље дефинишу тело функције.

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

Сада је могуће разумети и програме који садрже више од једног реда.

  • Да би се извршио низ наредби, извршити најпре прву наредбу. Уколико та наредба не преусмерава управљање, наставити са извршавањем преосталих наредби у низу, ако их има.

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

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

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

Дефинисање функција II: локална додела

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

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

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

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

Наравно, функција проценатРазлике се могла написати и као један израз, као што је приказано у наставку, али је повратни израз нешто сложенији.

>>> def проценатРазлике(x, y):
...     return 100 * abs(x-y) / x
>>> print(проценатРазлике(40, 50))
25.0

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

Условне наредбе

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

>>> abs(-2)
2

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

Ова имплементација функције апсолутнаВредност поставља неколико важних питања.

Условне наредбе

Условна наредба у Пајтону се састоји из низа заглавља и пакета наредби: обавезна if клаузула, необавезан низ elif клаузула, и коначно необавезна else клаузула:

if <израз>:
   <пакет>
elif <израз>:
   <пакет>
else:
   <пакет>

Приликом извршавања условне наредбе, свака клаузула се разматра по редоследу. Рачунски поступак извршавања условних клаузула следи.

  1. Вредновање израза у заглављу.

  2. Уколико је његова вредност тачна, изврши увучени пакет наредби који следи и прескочи све наредне клаузуле у условној наредби.

Уколико се дође до else клаузуле (што се само догађа ако се if и сви elif изрази вреднују у нетачну вредност), тада се њен увучени пакет наредби израчунава.

Логички контекст

Горњи поступак извршавања помиње „нетачну вредност” и „тачну вредност”. Изрази унутар заглавља условних наредби су у такозваном логичком контексту: њихова вредност је од значаја за управљање током извршавања, али иначе та вредност нити бива враћена нити додељена. Пајтон обухвата неколико нетачних вредности које укључују 0, None и логичку вредност False. Сви други бројеви имају тачне вредности. У следећем поглављу, биће приказано како свака уграђена врста података у Пајтону има и тачне и нетачне вредности.

Логичке вредности

Пајтон има две логичке вредности, и то True и False. Логичке вредности представљају вредности у логичким изразима. Уграђени оператори поређења, >, <, >=, <=, ==, !=, враћају ове вредности.

>>> 4 < 2
False
>>> 5 >= 5
True

Други пример има значење „5 је веће или једнако од 5”, и одговара функцији ge из operator модула.

>>> 0 == -0
True

Овај последњи пример има значење „0 је једнака -0”, и одговара функцији eq из operator модула. Ваља запазити да Пајтон разликује доделу (=) од провере једнакости (==), што је конвенција која важи у многим програмским језицима.

Логички оператори

Три основна логичка оператора су такође уграђени у Пајтон:

>>> True and False
False
>>> True or False
True
>>> not False
True

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

За вредновање израза <леви> and <десни>:

  1. Вредновати подизраз <леви>.

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

  3. Иначе, израз се вреднује у вредност подизраза <десни>.

За вредновање израза <леви> or <десни>:

  1. Вредновати подизраз <леви>.

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

  3. Иначе, израз се вреднује у вредност подизраза <десни>.

За вредновање израза not <израз>:

  1. Вредновати <израз>; Вредност је True ако је резултат израчунавања нека од нетачних вредности, а False иначе.

Ове вредности, правила и оператори обезбеђују начин комбиновања резултата поређења. Функције које врше поређења и враћају логичку вредност често почињу са is без доње црте у наставку, као isfinite, isdigit, isinstance, примера ради.

Итерација

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

Размотрити Фибоначијев низ бројева у коме је сваки члан једнак збиру претходна два:

0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, …

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

Може се искористити while наредба да се изброји n-ти Фибоначијев број. Потребно је пратити колико је чланова израчунато (k), као и вредности k-тог члана (тренутни) и његовог претходника (претходни). У променљивој тренутни смењиваће се Фибоначијеви бројеви баш као у наредној функцији.

Треба упамтити да запете раздвајају вишеструка имена променљивих и вредности унутар наредбе доделе. Ред:

претходни, тренутни = тренутни, претходни + тренутни

има за последицу превезивања имена претходни на вредност од тренутни и истовременог превезивања имена тренутни на вредност од претходни + тренутни. Сви изрази који се налазе с десне стране = се израчунавају пре него што се превезивање заправо деси.

Редослед догађаја — израчунавање свега с десне стране = пре ажурирања и повезивања леве стране — је суштински за исправност ове функције.

Клаузула while садржи заглавље израза праћен увученим пакетом наредби:

while <израз>:
    <пакет>

За извршавање while клаузуле:

  1. Вредновати израз из заглавља.

  2. Уколико је вредност тачна, извршити увучени пакет наредби и вратити се на претходни корак.

У другом кораку, читав увучени пакет наредби while клаузуле је извршен пре него што је израз из заглавља поново вреднован.

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

Наредба while која се никада не завршава се још назива и бесконачна или мртва петља. Притиском на <Control> + C може се приморати Пајтонов интерпретатор да заустави извршавање петље.

Тестирање

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

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

Провере

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

>>> assert фиб(8) == 13, 'Осми Фибоначијев број би требало да је 13'

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

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

>>> def фибТест():
...     assert фиб(2) == 1, 'Други Фибоначијев број би требало да је 1'
...     assert фиб(3) == 1, 'Трећи Фибоначијев број би требало да је 1'
...     assert фиб(50) == 7778742049, 'Грешка на педесетом Фибоначијевом броју'

Када се Пајтон код записује у датотеку, уместо директно у интерпретатор, тестови се типично пишу у истој или у суседној датотеци са наставком _test.py или _тест.py.

Доктестови

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

>>> def збирБројева(n):
...     """Враћа збир првих n природних бројева.
...
...     >>> збирБројева(10)
...     55
...     >>> збирБројева(100)
...     5050
...     """
...     збир, k = 0, 1
...     while k <= n:
...         збир, k = збир + k, k + 1
...     return збир

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

>>> from doctest import testmod
>>> testmod()
TestResults(failed=0, attempted=0)

Да би се проверила доктест интеракције на само једној функцији, користи се доктест функција под називом run_docstring_examples. Ова функција је (нажалост) нешто сложенија за позивање. Њен први аргумент је функција која се тестира. Други аргумент је резултат који враћа позив уграђене Пајтонове функције globals(). Трећи аргумент је True да назначи „опширан” излаз: списак свих покренутих тестова.

>>> from doctest import run_docstring_examples
>>> run_docstring_examples(збирБројева, globals(), True)
Finding tests in NoName
Trying:
    збирБројева(10)
Expecting:
    55
ok
Trying:
    збирБројева(100)
Expecting:
    5050
ok

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

Када је Пајтон код записан у датотеци, сви тестови из датотеке се могу покренути уз Пајтонову доктест опцију у командној линији:

python3 -m doctest <имеДатотеке>

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