U prijašnjim ste brojevima mogli naučiti sve što vam je potrebno za izradu programa u Pythonu 3.x. Međutim, znanjem stečenim u prethodnim člancima ne možete definirati vlastite funkcije kojima ćete skratiti i olakšati programiranje. Donosimo vam osnove rada s ručno definiranim funkcijama u Pythonu: moći ćete nekakav zadatak koji se ponavlja unutar istoga programa izvršavati pomoću vlastite funkcije, koju možete pozvati u bilo kojem dijelu programa te taj zadatak odraditi bez potrebe za ponovnim unosom većeg dijela kôda.
Obradit ćemo i osnove rada s datotekama pa ćete nakon pažljivog čitanja zaista moći napraviti program koji može za vas imati veliku uporabnu vrijednost. Općenito, u svim primjerima, naglasak će biti na radu sa stringovima, jer Python podržava čitav niz vrlo korisnih funkcija za rad sa stringovima. Pošto je Python objektno orijentiran programski jezik, string je jedna od mnogih klasa objekata u Pythonu te je upravo zbog toga rad sa stringovima brz i intuitivan. Naposljetku, većina postojećih programa obavljaju zadatke nad stringovima jer svaki dokument koji se obrađuje ili tekst kojeg korisnik unosi, a koji treba biti obrađen, podrazumijeva upravo rad sa stringovima.
Funkcije
Za početak, prvi primjer je program koji od korisnika traži unos rečenice u terminal. Program čita unose, dakle, više rečenica, sve dok korisnik ne unese riječ “exit”. Za svaki unos, program odmah ispisuje istu rečenicu s time da postavlja veliko slovo na početku rečenice te na kraju stavlja točku ukoliko je nema. Prvi primjer možete vidjeti u okviru sa strane kao i screenshot pri izvršavanju. Napominjemo, prvi primjer ne sadrži ručno definiranu funkciju. U prvoj se liniji nalazi prvi unos korisnika koji će se obrađivati. Ostatak programa odnosno svi zadaci koje program obavlja smješteni su u while petlju kojoj je uvjet ostanka da je uneseni tekst različit od “exit”. Prvo se u petlji nad unesenim tekstom vrši metoda capitalize (). Ta metoda vraća kopiju stringa nad kojim se vrši metoda, a prvo slovo se pretvara u veliko ukoliko je bilo malo. Kao što se vidi i u primjeru izvođenja, metoda je vrlo moćna jer podržava i nestandardne znakove.
Metoda ispravlja znakove u cijeloj rečenici na način da sva slova pretvara u mala slova, a prvo slovo rečenice pretvara u veliko slovo. U drugoj liniji unutar petlje provjerava se kraj unesenog teksta te ukoliko je on različit od točke, upitnika i uskličnika, na tekst se dodaje točka. Nakon toga se ispisuje ispravljena rečenica, ponovno se učitava tekst te se program nastavlja izvoditi na ispitivanju uvjeta ostanka u petlji. Možda se pitate čemu taj primjer i kakve on ima veze s funkcijama? Naime, u takvom programu zaista nema potrebe za upotrebom ručno definirane funkcije. Petlja while osigurava mogućnost višestrukog obavljanja iste zadaće odnosno izvodi jednake obrade za više linija teksta. U našem slučaju, uvjet je jednostavan, a obrada se izvodi pri svakom unosu novog teksta.
Međutim, što ako određene unose želimo preskočiti ili se npr. ukaže potreba da jednom petljom izvrtimo nekoliko unosa, zatim izvršimo nekakvu obradu, a nakon toga opet pomoću petlje ispravljamo rečenice? Tada bismo morali sadržaj cijele petlje kopirati i zalijepiti ga u tu novu petlju te bi tada dio kôda bio bespotrebno više puta upisan u istom programu. Drugi primjer obavlja identičnu zadaću kao prvi, razlika je samo u načinu pisanja programa – definiramo vlastitu funkciju imena ispraviRecenicu koja ima jedan argument “arg”. Od prve do pete linije kôda je definicija nove funkcije. Definicija započinje ključnom riječi “def” iza koje se navodi ime funkcije te se u zagrade navode argumenti koje funkcija podržava.
U našem slučaju to je jedan jedini argument koji je nazvan “arg”. Cjelina koja se nalazi unutar definicije funkcije, a ne pripada headeru koji počinje ključnom riječi “def”, često se naziva tijelom funkcije. Vidimo da se u našem primjeru sadržajem ne razlikuje puno od glavne obrade iz prvog primjera; razlika je samo u upotrebi nove varijable arg i liniji [return(arg)]. Vratit ćemo se još na samu definiciju funkcije. U glavnom dijelu programa vidimo da je cijela obrada, koja je prebačena u definiciju funkcije, svedena na poziv funkcije linijom [tekst = ispraviRecenicu(tekst)].
Što se zapravo događa ovim pozivom? Kopija varijable tekst se kao argument predaje funkciji naziva ispraviRecenicu. Izvođenje programa se nastavlja na prvoj liniji kôda, gdje se sadržaj varijable “tekst” kopira u varijablu “arg” te se u funkciji obrađuje varijabla arg. Na kraju funkcije, naredbom povratak vraća se vrijednost varijable arg. Program nastavlja izvođenje gdje je stao prije poziva funkcije te se varijabli “tekst” dodjeljuje sadržaj varijable “arg” nakon obrade unutar funkcije. Dakako, sadržaj se svih varijabli unutar definicije funkcije gubi prilikom završetka izvođenja funkcije. Završetak izvođenja funkcije se određuje naredbom povratak odnosno ukoliko funkcija ne vraća nikakav rezultat, krajem tijela funkcije (poništenjem uvlake). Općenito kod funkcija postoji nekoliko bitnih stvari za zapamtiti. Prvo, sve definicije funkcija moraju se pisati prije prvog poziva funkcije. To je nužno zbog načina rada većine programskih jezika pa tako i Pythona. Naredbom defdefinira se funkcija pa ukoliko ona nije prvo definirana, a zatim pozvana, Python neće prepoznati što predstavlja tekst “ispraviRecenicu(tekst)”.
Kada je funkcija definirana, njen naziv je pohranjen u posebnu tablicu (koja je korisniku pa i programeru nevidljiva) kako bi eventualnim kasnijim pozivom Python “znao” da je to funkcija i gdje se nalazi njena definicija. Nadalje, definicija funkcije se ne izvršava automatski nego tek pozivom. To znači sljedeće: program se neće izvršavati od prve linije kôda (u našem primjeru) nego tek od sedme linije. Definicije svih funkcija se preskaču, a linije unutar definicije se izvršavaju jedino pozivom funkcije. Drugi primjer ćemo nadograđivati kroz članak, no sada ćemo napraviti blagu digresiju kako bismo objasnili važnost poznavanja rada Pythona pri definiranju vlastitih funkcija. Argumenti i povratna vrijednost funkcije
Treći primjer donosi rad s listama pomoću ručno definirane funkcije. U screenshotu možete vidjeti što program radi: za upisana dva cijela broja ispisuje jesu li poredani odnosno je li prvi manji od drugoga. Program ih sprema u listu pri samom upisu, a ako nisu poredani, premješta ih tako da prvi član bude manji od drugoga. Prvo ćemo objasniti glavni dio programa. U varijablu naziva “brojevi” odmah se upisuju prvi i drugi broj te je varijabla tipa liste. Članovi, pošto su stringovi jer su uneseni s tipkovnice, funkcijom int() se pretvaraju u cjelobrojne vrijednosti te nakon toga spremaju u listu. U drugoj liniji kôda imamo obavljenu veliku većinu zadaće glavnog programa. U uvjetu naredbe if ispituje se vraća li funkcija poredaj() istinu, za listu koja joj je predana kao argument. Kao što već vjerojatno znate, istina se može predočiti i kao broj 1, a laž kao broj 0.
Definiciju funkcije poredaj() objasnit ćemo uskoro – zasad je dovoljno znati da ona provjerava listu i izmjenjuje je ukoliko je prvi broj veći od drugoga. Ako je prvi broj manji od drugoga, funkcija vraća 1, a ako nije, funkcija vraća 0 i zamjenjuje članove liste. Naposljetku, ako je funkcija vratila 1, ispisuje se poruka da su brojevi bili poredani. U suprotnom, ispisuje se da nisu bili poredani. Primjećujete da ako blok naredbe if (kao i for, while, else itd.) sadrži samo jednu naredbu, možemo je pisati u nastavku headera, bez potrebe za prelaskom u novi red i uvlačenjem retka. Na kraju glavnog programa ispisuje se lista “brojevi”.
Primjećujete da su brojevi koje je korisnik unio spremljeni upravo u tu istu varijablu te nema pridruživanja novih vrijednosti toj varijabli u obliku [varijabla = funkcija(varijabla)], kao što je to bio slučaj u prošlom primjeru. No, ipak, funkcija je izmijenila listu.
Zašto? Sada upravo dolazimo odnosno vraćamo se na temu iz prvog članka o Pythonu, a to je pitanje izmjenjivih i neizmjenjivih objekata. Lista je izmjenjiv objekt dok je string neizmjenjiv. Zato, ako u funkciju prosljeđujemo string, funkciji se predaje kopija stringa odnosno moglo bi se reći sadržaj varijable. Obavlja se tzv. prenošenje vrijednosti parametra te bilo kakva izmjena te vrijednosti unutar funkcije ne mijenja sadržaj originalne varijable čija je vrijednost predana funkciji. Zato se pri obradi stringa uvijek varijabli eksplicitno mora pridružiti vrijednost koju funkcija vraća.
U suprotnom bi se izmijenjena vrijednost trajno izgubila jer sve varijable unutar funkcije “žive” samo od poziva do završetka iste. U slučaju liste, situacija je drukčija. Pošto je lista izmjenjiv objekt, pri predaji liste funkciji prenosi se njena adresa. Takvo ponašanje bi se moglo usporediti s predavanjem pointera funkciji u programskom jeziku C te se takvo prenošenje naziva još i prenošenje reference parametra. Prenošenjem adrese, funkcija ima pristup upravo varijabli koja joj je prenesena te svaka izmjena takve promjenjive varijable unutar funkcije, rezultira izmjenom originalne varijable koja je predana funkciji. Zbog toga, nije potrebno eksplicitno obaviti pridruživanje povratne vrijednosti funkcije varijabli te se varijabla može izmijeniti jednostavnim pozivom oblika [funkcija(varijabla)]. To je i učinjeno u trećem primjeru.
Mi smo u našem slučaju iskoristili sposobnost funkcije da vrati rezultat u drugu svrhu: ispitivanje nekog svojstva varijable koja je proslijeđena u nju. Lista se “na licu mjesta” posloži ako je potrebno, a funkcijom vraćamo istinitost tvrdnje da je lista bila poredana. Program bi jednako dobro funkcionirao, iako bez ispisa je li lista bila poredana, kada bismo umjesto druge linije glavnog dijela programa upisali liniju [poredaj(brojevi)] te uklonili sljedeću liniju kôda (ne smijemo ostaviti else ako nema naredbe if prije nje). Definicija funkcije nije komplicirana, ali valja obratiti pažnju na par stvari. Prvo, ulaskom u blok naredbe if obavlja se zamjena prvoga i drugoga člana liste te funkcija vraća vrijednost 0.
Pri izvršavanju funkcije return(), funkcija prestaje s radom pa zadnja linija u tijelu funkcije [return(1)] neće biti niti izvršena. Zbog toga nije potrebna naredba else i pripadajući blok u koji bismo stavili samo liniju [return(1)]. Preporučujemo da dobro zapamtite princip zamjene vrijednosti dvaju varijabli, ukoliko se niste prije susretali s njim. To je klasičan pristup koji se praktički uvijek koristi kada je potrebno zamijeniti sadržaje dvije varijable. Potrebna je dodatna varijabla koja je ovdje imenovana “temp”, što je opet prilično raširen naziv za varijablu koju smatramo privremenom (temp je skraćenica od engleske riječi “temporary” čiji je prijevod “privremen”). U privremenu varijablu spremamo vrijednost prve varijable (prvoga člana liste), nakon toga u prvu varijablu spremamo vrijednost druge varijable te naposljetku vrijednost privremene varijable (koja je jednaka prvotnoj vrijednosti prve varijable) spremamo u drugu varijablu.
Time je prvoj varijabli pridružena vrijednost druge, a drugoj vrijednost prve. Ovdje smo argument nazvali listaArg. Dobro je imati u navici da se argumentima dodaje nekakav prefiks ili sufiks koji se dodaje nazivima svih argumenata jer u većim programima lako se izgubiti u gomili varijabli koje predstavljaju argumente i onih koje su izvan funkcije. Dodavanjem npr. sufiksa “Arg”, kao što smo mi učinili, bez dvojbe znate da je riječ o argumentu funkcije u kojoj se varijabla nalazi. Proširenje drugog primjera
U četvrtom primjeru je poboljšana funkcionalnost drugog primjera. Poboljšanja su sljedeća: samo prvoj riječi u rečenici se postavlja veliko početno slovo te se izbacuju uskličnici, upitnici i točke na krajevima riječi unutar unesenog teksta (na kraju se zadržavaju, kao i u drugom primjeru). Za početak, imamo dvije ručno definirane funkcije.
Druga poboljšano ispravlja rečenicu dok prva služi za provjeru završava li riječ koja je predana kao argument točkom, uskličnikom ili upitnikom te poduzima potrebne radnje nad tom riječi. Redoslijed definiranja te dvije funkcije je bitan jer funkcija ispraviRecenicu() koristi funkciju oznakaKraja(). Funkcija oznakaKraja() prima dva argumenta: prvi je argument riječ koju se provjerava dok drugi argument govori je li potrebno na učitanu riječ dodati točku ili ne. Vidimo da je u headeru te funkcije upisano “dodajArg = 1”.
Ta jednakost govori da je pretpostavljena vrijednost toga argumenta 1 što znači da funkciju smijemo pozvati bez predaje drugoga [tab]argumenta te će u tom slučaju on poprimiti vrijednost 1. Ako se pri pozivu funkcije ipak proslijede dva argumenta, varijabla dodajArg će sadržavati drugi predani argument. Obično se u praksi programiranja funkcija modelira tako da je ili pretpostavljena vrijednost logična (npr. definicija neke funkcije zadana pomoću [def obradi(arg1, ispisiGresku=1)] koja je modelirana da po običaju ispisuje pogrešku) ili se pretpostavljena vrijednost puno češće koristi od svih ostalih (npr. [def potenciraj(broj, potencija=2)] jer će se broj najčešće kvadrirati).
U našem slučaju, pretpostavljena vrijednost je 0 jer nulom, u našem programu, podrazumijevamo potrebu za uklanjanjem jednoga od triju spomenutih znakova na kraju riječi unutar rečenice dok jedinicom podrazumijevamo dodavanje znaka na kraj rečenice ukoliko je potrebno. Naravno, više će se provjera obaviti unutar rečenica jer je više riječi unutar rečenica nego na kraju njih.
Iako za naš primjer nije izbor pretpostavljene vrijednosti argumenta od velikog značaja, jer nam promjena vrijednosti ne skraćuje vrijeme pisanje programa. Ipak, dobro je imati na umu ideju zašto uopće pretpostavljati vrijednost nekog argumenta. U funkciji oznakaKraja(), ukoliko riječ ne završava niti jednim od kritičnih znakova, provjerava se je li varijabla dodajArg istinita.
Ako jest, riječi se dodaje točka, preskače se else blok i vraća se izmijenjena riječ. Primjećujete da su ta dva uvjeta mogla biti stavljena u jednu if naredbu, no zbog bolje preglednosti, to nije učinjeno. Blok naredbe predstavlja slučaj kada riječ završava nekim od kritičnih znakova. Ako završava i ako je varijabla dodajArg jednaka nuli, riječ se skraćuje za jedan znak. Uzima se dio riječi od prvoga do predzadnjega znaka te se taj dio kopira u istu varijablu.
Nakon svega slijedi vraćanje obrađene riječi pomoću funkcije return(). U funkciji ispraviRecenicu() na početku string pretvaramo u listu, a kao odjeljivač koji određuje gdje će string presijecati, pretpostavljen je razmak. U varijablu tempList pohranjuje se takva novo dobivena lista. Pomoću for petlje iterira se po svim članovima liste i to tako da se varijabli “i” dodjeljuju svi brojevi od 0 do broja članova liste. Na prvi pogled se čini da se moglo iterirati i pomoću [for rijec in temList], no to bi zakompliciralo kôd i produljilo vrijeme njegovoga upisivanja.
Naime, tada bismo teško mogli odrediti koja je točno riječ zadnja riječ u unosu. Morali bismo na neki način prebrojavati koliko je članova liste obrađeno i kolika je duljina liste. Tako je to prebrojavanje izvedeno u samom headeru petlje bez potrebe za eksplicitnom definicijom brojača, inicijalizacijom i inkrementiranjem istoga. Uvjet prve if naredbe je da riječ koja se obrađuje nije zadnja u tekstu. Riječ nije zadnja upravo ako je njen indeks manji od duljine liste umanjene za jedan. Naime, ako unos sadrži pet riječi, zadnja riječ ima indeks 4, a sve prethodne riječi imaju indeks manji od 4. U tom slučaju, riječ se obrađuje pomoću opisane funkcije oznakaKraja() pri čemu je implicitno određeno izostavljanjem drugog argumenta da se treba maknuti kritični znak s kraja riječi ukoliko on postoji.
Naredba else podrazumijeva sve ostale slučajeve odnosno jedan preostali slučaj, a to je da je riječ obrade zadnja u unesenom tekstu. Tada funkciji oznakaKraja() prosljeđujemo i argument 1 pomoću kojeg će funkcija “znati” da je potrebno dodati točku na kraj riječi, ukoliko riječ ne završava kritičnim znakom. Time je postignuta selektivna manipulacija kritičnim znakovima: uklanjaju se tamo gdje nisu potrebni, a stavljaju tamo gdje jesu. Dvije su vrlo slične funkcije objedinjene u jednu koja pokriva velik dio zajedničkoga posla. Napravljena je velika ušteda vremena i prostora pri kodiranju, a i program je fleksibilniji za izmjene.
Funkciji je lako dodati mnoštvo drugih opcija ukoliko se ukaže potreba za njima. Sve je navedeno glavni cilj upotrebe funkcija koji je u ovome slučaju postignut. Već prije upotrijebljena metoda capitalize() sad se lako primjenjuje samo na prvoj riječi unesenog teksta jer je tekst “razbijen” u listu. Jednostavno metodu primijenimo samo na prvu riječ odnosno na nulti član liste. I na kraju funkcije ispraviRecenicu vraća se obrađeni tekst tako što se lista pomoću metode join() pretvara natrag u string te vraća funkcijom return() u glavni program.
Glavni je dio programa jednak onome u drugom primjeru. Sva važnija znanja potrebna za definiranje vlastite funkcije su iznesena. Bitno je poznavati sustav predaje argumenata funkciji jer nekad ćete definirati funkciju za listu, a nekad za string – kod liste se varijabla izmjenjuje u funkciji dok se kod stringa izmjenjuje samo kopija varijable, odnosno njezin kopirani sadržaj. Postoje programi kod kojih ne treba forsirati upotrebu funkcija. Ukoliko se jedan zadatak ne obavlja više puta unutar programa ili se ponavlja, ali se ponavlja zbog petlje, nema potrebe za upotrebom funkcije.
Tako npr. u drugom primjeru je samo demonstrirana upotreba funkcije kako bi se vidjele razlike u kôdu između prvog i drugog primjera. Realno, rješavanje zadanoga problema kôdom iz drugog primjera je gubljenje vremena jer je kôd duži. Ipak, ukoliko se radi o programu koji će biti potrebno proširivati (kao što je ovdje slučaj) isplati se uložiti nešto više vremena pa zadatak koji se proširuje preoblikovati u funkciju. Sve ovisi o situaciji u kojoj se programer nalazi te kakvi su mu planovi za budućnost glede programa na kojem radi. Sljedećim vam brojem donosimo nastavak škole, a do tada sretno s Pythonom!
Infobox (facts): Prvi primjer
tekst = str(input(‘Unesite recenicu: ‘))
while(tekst != ‘exit’):
[tab]tekst = tekst.capitalize()
[tab]if(tekst[-1] != ‘.’ and tekst[-1] != ‘?’ and tekst[-1] != ‘!’):
[tab][tab]tekst += ‘.’
[tab]print(‘Ispravljena recenica: ‘ + tekst)
[tab]tekst = str(input(‘Unesite recenicu: ‘))
Infobox (facts): Drugi primjer
def ispraviRecenicu(arg):
[tab]arg = arg.capitalize()
[tab]if(arg[-1] != ‘.’ and arg[-1] != ‘?’ and arg[-1] != ‘!’):
[tab][tab]arg += ‘.’
[tab]return(arg)
tekst = str(input(‘Unesite recenicu: ‘))
while(tekst != ‘exit’):
[tab]tekst = ispraviRecenicu(tekst)
[tab]print(‘Ispravljena recenica: ‘ + tekst)
[tab]tekst = str(input(‘Unesite recenicu: ‘))
Infobox (facts): Treći primjer
def poredaj(listaArg):
[tab]if(listaArg[0] > listaArg[1]):
[tab][tab]temp = listaArg[0]
[tab][tab]listaArg[0] = listaArg[1]
[tab][tab]listaArg[1] = temp
[tab][tab]return(0)
[tab]return(1)
brojevi = [int(input(“Prvi broj: “)), int(input(“Drugi broj: “))]
if(poredaj(brojevi)): print(“Bili su poredani.”)
else: print(“Nisu bili poredani.”)
print(brojevi)
Infobox (): Četvrti primjer
def oznakaKraja(strArg, dodajArg = 0):
[tab]if(strArg[-1] != ‘.’ and strArg[-1] != ‘?’ and strArg[-1] != ‘!’):
[tab][tab]if(dodajArg): strArg += “.”
[tab]else:
[tab][tab]if(not dodajArg): strArg = strArg[:-1]
[tab]return(strArg)
def ispraviRecenicu(arg):
[tab]tempList = arg.split()
[tab]for i in range(len(tempList)):
[tab][tab]if(i < len(tempList) – 1):
[tab][tab][tab]tempList[i] = oznakaKraja(tempList[i])
[tab][tab]else:
[tab][tab][tab]tempList[i] = oznakaKraja(tempList[i], 1)
[tab]tempList[0] = tempList[0].capitalize()
[tab]return(” “.join(tempList))
tekst = str(input(‘Unesite recenicu: ‘))
while(tekst != ‘exit’):
[tab]tekst = ispraviRecenicu(tekst)
[tab]print(‘Ispravljena recenica: ‘ + tekst)
[tab]tekst = str(input(‘Unesite recenicu: ‘))
Autor: Josip Užarević