Materjalid koostas ja kursuse viib läbi
Tartu Ülikooli arvutiteaduse instituudi programmeerimise õpetamise töörühm
< eelmine | 6. nädala sisukord | järgmine > |
6.1 Alamprogramm. Funktsioon
Kui on mingi suurem ülesanne lahendada, siis sageli on mõistlik lahendus osadeks jaotada. Alamülesanded on väiksemad ja võib-olla saab mõne nendest hoopis kellelegi teisele teha anda. Kui käsil on maja ehitamine, võib olla mõistlik näiteks kamin lasta ehitada kogenud pottsepal. Kodu koristamise käigus võib näiteks prügiämbri väljaviimise usaldada kellelegi usaldusväärsele inimesele, kes selle töö kiiresti ja korralikult ära teeb ning seejuures ise kaotsi ei lähe.
Programmeerimise juureski on suure ülesande jaotamine osadeks äärmiselt oluline. Ühelt poolt võimaldab see programmi kirjutamise jaotada erinevate inimeste (osakondade, firmade) vahel ja nii ratsionaalsemalt tegutseda. Näiteks saab kogu programm kokkuvõttes siis varem valmis, kui tööd tehakse paralleelselt (tööaega ei pruugi summaarselt küll vähem kuluda). Põhimõtteliselt on olemas võimalused ka programmi enda töö paralleliseerimiseks, aga neid me selles kursuses ei käsitle.
Tegelikult ei huvita meid siin niivõrd, mitu inimest programmi erinevaid osi kirjutab, vaid see võimalus, et juba valmis tehtud alamprogramme saab korduvalt kasutada. Seda võibki lugeda alamprogrammi idee üheks põhialuseks. See ideoloogia on kooskõlas ka nn DRY-printsiibiga (ingl k “Don’t repeat yourself” (“Ära korda ennast”)), millega rõhutatakse, et samasuguse koodi mitmekordset kirjapanekut tuleks vältida. Tegelikult oli selle põhimõttega täiesti kooskõlas juba see, et mingite ridade korduva kirjapaneku asemel lasime neid korduvalt täita tsüklil.
OLEME FUNKTSIOONE JUBA KASUTANUD
Erinevates programmeerimiskeeltes (või ka erinevates programmeerimist käsitlevates materjalides) võivad alamprogrammide nimetused ja liigitamine olla mõnevõrra erinevad. Pythonis tegutseme funktsioonidega, eespool oleme neid vahel ka käskudeks nimetanud.
Tegelikult oleme funktsioonidega kokkupuutunud juba esimesest nädalast alates. Näiteks juba esimestest programmidest on meil kasutuses funktsioon print
, millega oleme ekraanile erinevaid suurusi väljastanud. Üsna algusest oleme kasutanud ka funktsiooni input
, millega oleme kasutajalt infot saanud. Ka funktsioonid round
, randint
, forward
, back
, left
, right
, color
, exitonclick
jt on tuttavad. Funktsiooni nime järel on sulud, milles võivad olla (ja sageli ongi) argumendid, nt forward(100)
viib kilpkonna 100 sammu edasi. Aga oli ka ilma argumentideta funktsioone, nt exitonclick()
. Funktsiooni range
puhul on 1, 2 või 3 argumenti.
Mitme argumendiga funktsioonide puhul on väga oluline argumentide järjekord. Näiteks range(2, 10, 3)
ja range(3, 10, 2)
tähendavad erinevaid asju.
Eelmainitud funktsioonid on Pythonisse sisseehitatud. Teisisõnu, need on kirjutatud Pythoni arendajate poolt. Paljud funktsioonid on kohekasutatavad, osade puhul on vaja vastav moodul importida. Paraku ei tunne Pythoni arendajad kõikide programmide ja programmeerijate vajadusi, seega oleks päris kasulik, kui saaksime nende poolt kirjutatud funktsioonidele lisaks ka ise meile vajalikke funktsioone juurde kirjutada. See on täiesti võimalik! Tegelikult programmide kirjutamine suuresti just uute funktsioonide loomises seisnebki.
Funktsiooni defineerimiseks ehk kirjeldamiseks kasutatakse võtmesõna def. Igal funktsioonil on nimi, mille abil saame teda hiljem kasutada. Järgmises näites on funktsioonile antud nimi trükiAB. Põhimõtteliselt on meil nime valikuks üsna vabad käed (sarnaselt muutuja nime valikuga), aga hea programmeerimise stiil on kasutada nime, mis kajastab seda, mida funktsioon teeb. Funktsiooni nimele järgnevad sulud, mille praegu jätame tühjaks (argumente pole). Hiljem vaatame ka seda, mida huvitavat sulgude sisuga teha saab. Pärast sulge tuleb koolon ning kõik järgnevad read, mis antud funktsiooni alla kuuluvad, peavad olema esimese rea (rida, kus asub def ja funktsiooni nimi) suhtes taandatud.
def trükiAB(): print("A") print("B")
Kui meie programm koosneb ainult nendest kolmest reast, siis selle käivitamisel ei juhtu näiliselt midagi. A-d ja B-d ekraanile ei ilmu, kuigi võiks ju! Oleme funktsiooni küll kirjeldanud (defineerinud), aga ei ole seda veel rakendanud (välja kutsunud). Sisuliselt oleme Pythonile “õpetanud” selgeks, kuidas reageerida uuele käsule. Varem sai Python aru näiteks käskudest print ja input, aga nüüd saab aru ka käsust trükiAB!
Kui tahame funktsiooni rakendada, siis saame seda teha samas programmis. Selleks tuleb eraldi reale kirjutada funktsiooni nimi (meil trükiAB) ning tühjad sulud. Rakendame funktsiooni lausa kaks korda.
def trükiAB(): print("A") print("B") print("C") trükiAB() print("D") trükiAB()
Proovige, mis ilmub ekraanile.
Esimesel real antakse teada, et nüüd hakatakse funktsiooni kirjeldama. Funktsiooni kirjelduse ulatust näitab taane. Teine ja kolmas rida on seega veel funktsiooni osad. Programmi tegelik täitmine algab alles realt print("C")
, sest eelmised read vaid kirjeldasid funktsiooni. Jätame enne n-ö põhiprogrammi ka ühe tühja rea. See on inimese, mitte Pythoni jaoks. Funktsiooni kirjeldamine ei too endaga kaasa meie jaoks nähtavaid muutusi. Küll aga saame võimaluse seda funktsiooni kasutada.
Kui nüüd programmi käivitame, siis ekraanile ilmub esimesena just print("C")
toimel C. Edasi rakendatakse funktsiooni trükiAB, mille mõjul ilmuvad ekraanile A ja B. Seejärel toob rida print("D")
ekraanile D. Pärast seda rakendatakse jälle funktsiooni trükiAB.
Ülesanne
Veel tuleb tähele panna, et programmis peavad funktsioonid olema kirjeldatud enne nende väljakutsumist. Niisamuti nagu me ei saa inimeselt oodata oskust liita 1-le 2, kui me pole talle liitmist õpetanud, ei saa ka Pythonilt nõuda käskude (funktsioonide) täitmist enne, kui need on talle õpetatud (defineeritud). Seepärast on tavaks funktsioonide definitsioonid kirjutada kohe programmi algusesse, et neid saaks kogu järgneva programmi jooksul vajadusel välja kutsuda.
Proovige järgmist näidet.
trükiAB() def trükiAB(): print("A") print("B")
Mis juhtus? Miks?
Veateateid ei maksa karta, nendega püütakse edasi anda olulist informatsiooni. Programmeerija töö oleks ilma veateadeteta oluliselt keerulisem!
Ülesanne
Funktsiooni võib välja kutsuda ka tsüklis. Näiteks järgmises programmis töötab funktsioon trükiAB
10 korda.
def trükiAB(): print("A") print("B") for i in range(10): trükiAB()
FUNKTSIOONI KIRJELDUSES
Funktsiooni kirjelduses saab kasutada neid funktsioone, millest Python “aru saab”. Sageli kasutatakse neid, mis juba vaikimisi Pythonis olemas on. Kasutasime ju meiegi funktsiooni print funktsiooni trükiAB kirjelduses. Tegelikult saab kirjelduses kasutada ka funktsioone, mis kellegi enda tehtud on. Nii on järgmises programmis defineeritud funktsioon funktsioon_a()
ja seda on kohe järgmise funktsiooni kirjelduses kasutatud.
def funktsioon_a(): print("a") def funktsioon_b(): funktsioon_a() print("b")
Katsetage mõlema funktsiooni tööd! Selleks peate need pärast kirjeldust ka välja kutsuma.
Ülesanne
Vaata ka kokkuvõtvat videot:
Tegelikult saab funktsioon välja kutsuda ka iseennast. Sellist olukorda nimetatakse rekursiooniks ja see on väga võimas võimalus. Rekursiooni abil saab näiteks sugupuust (või mingist muust puulaadsest struktuurist) konkreetset nime üles otsida. Rekursioon on küllaltki keeruline teema ja algkursuses seda reeglina ei käsitleta. Meie siiski silmaringi materjalis rekursiooni vaatleme.
Funktsiooni kirjelduses saab kasutada ka teisi konstruktsioone peale funktsioonide väljakutsete. Näiteks varasemalt kasutatud "alla lugemise" saame esitada funktsioonina. Näeme, et siin on taane mitmeastmeline, sest tsükkel on funktsiooni sees.
from time import sleep def loe_alla(): i = 10 while i > 0: print(i) i -= 1 sleep(1) loe_alla()
Funktsioone võib tinglikult jaotada kahte rühma: ühte tüüpi funktsioonide puhul me tahame, et nad midagi ära teeksid ja teiste puhul, et nad midagi arvutaksid ning meile tulemuse tagastaksid. Senised selle osa funktsioonid tegid midagi ära - näiteks väljastasid midagi ekraanile. Tegelikult on selline ka funktsioon print, mida oleme korduvalt varem kasutanud. Funktsioonile print andsime ikka ette, mida tahtsime ekraanile saada.
print("Sisesta PIN-kood:")
Funktsioonile etteantavaid suurusi nimetatakse argumentideks ning need pannakse funktsiooni väljakutsel funktsiooni nimele järgnevate sulgude sisse (nii toimime ka ju print funktsiooniga). Argumentidest tuleb juttu järgmises peatükis. Samuti tuleb seal juttu funktsioonidest, mille põhiroll ei ole millegi ärategemine, vaid hoopis mingisuguse tulemuse arvutamine.
KÄSUREAAKEN
Lisaks sellele, et funktsiooni saab rakendada programmi sees, saab seda teha ka Thonny käsureaaknas. See on Thonny osa, mille tiitelribale on kirjutatud Shell ja iga rea ees on >>>.
Olgu meil programm, milles on ainult funktsiooni kirjeldus, aga selle funktsiooni rakendamist pole.
def trükiAB(): print("A") print("B")
Kui nüüd nagu tavaliselt valida roheline nupp, F5 või Run-menüüst Run current script, siis käsureaaknas näidatakse vaid, mis failis olev programm käivitati.
Kui käsureale kirjutada trükiAB()
ja reavahetusklahvi vajutada, siis see funktsioon rakendub.
Meie jätkame siiski nii, et kirjutame funktsiooni rakendamised ikka programmiteksti sisse, kuid käsureal saab oma funktsioonide tööd eraldi mugavalt kontrollida.
Vaata ka kokkuvõtvat videot:
< eelmine | 6. nädala sisukord | järgmine > |