Imperativna (tj. proceduralna) programska paradigma je jednostavna - program se sastoji od niza deklaracija, naredbi pridruživanja te poziva predefiniranih kao i vlastitih potprograma. Potprogrami omogućuju smisleno strukturiranje programa. Ovo je prirodan i vrlo pristupačan način programiranja i stoga je razumljivo da je upravo takav pristup korišten u prvim popularnim programskim jezicima poput Fortrana ili C-a. Problem sa ovakvim pristupom programiranju je sve veća kompleksnost današnjih programa: što je program veći - teže ga je održavati.
Kod objektno orijentiranog programiranja (OOP) program se sastoji od skupa objekata koji međusobno komuniciraju. Objekt je programski entitet koji je sposoban primati, obrađivati i slati poruke (poruke su programski najčešće realizirane običnim funkcijskim pozivima i callback funkcijama). Objektno orijentirani model je izuzetno pogodan za opisivanje današnjih sustava koji se najčešće sastoje od jasno odvojenih, samostalnih cjelina koje međusobno komuniciraju i razmjenjuju obrađene podatke. Jedan od takvih primjera koji se najčešće navodi je grafičko korisničko sučelje (GUI).
Temelj funkcijskog programiranja te funkcijske programske paradigme leži u izrazima i evaluacijama. Ta dva pojma se mogu smatrati centralnim objektima promatranja u funkcijskom programiranju. Program se sastoji on niza izraza i evaluacija, a nakon svake evaluacije smo bliže rješenju (potrebno je obaviti još evaluacija) ili smo došli do konačnog rješenja. Prilikom evaluacije se primjenjuju razne tehnike poput rekurzije i ugnježđivanja funkcija. Ovakav programski model je pogodan za efikasan opis raznih matematičkih izraza te algoritima. Pošto većina takvih izraza koristi liste i skupove, funkcijski jezici su najčešće orijentirani na rad sa listama.
Jedan od najpoznatijih te najstarijih programskih jezika koji podržavaju funkcijsko programiranje je LISP (List Procesor) te razni jezici nastali po uzoru na njega (jedna od poznatijih je Scheme). LISP je funkcijski jezik, ali u njemu je moguće koristiti i drukčije programske paradigme, na primjer objektnu. S druge strane, postoje jezici koji su striktno funkcijski i ne podržavaju druge programske paradigme. Takav je na primjer programski jezik Haskell. Programski jezik Python podrzava čak tri programske paradigme: objektno orijentirano, imperativno te funkcijsko programiranje. Neki od ostalih poznatih programskih jezika koji su funkcijski ili podržavaju funkcijsko programiranje su: ML, Erlang, Logo...
>>> l = [] >>> l = [2, 5, 1, 3]Liste nisu poput polja u drugim programskim jezicima i ne moraju nužno sadržavati elemente istog tipa, a uz to mogu sadržavati i druge liste:
>>> l = ['pero', 1, 2, ['ivica', 'mirko', ['crocop', 2], 2]]Duljinu liste možemo saznati sa len(). Za prethodni primjer duljina je:
>>> len(l) 4Na inicijaliziranoj listi dostupan je niz operacija i metoda. Na primjer, element se dodaje na kraj liste pomoću metode append()
>>> l.append(5) >>> print l [2, 5, 1, 3, 5]Ispis liste smo mogli dobiti i samo navođenjem njenog imena (ovo naravno radi samo u interaktivnom načinu rada Python interpretera, dok je za ispis unutar programa potreban print).
Ukoliko je potrebno umetnuti novi član na specifično mjesto, a ne na kraj liste, onda koristimo metodu insert sintakse insert (pozicija, element). Numeriranje elemenata liste u Pythonu počinje sa nulom:
>>> l.insert (3, 7) >>> l [2, 5, 1, 7, 3, 5]Metoda pop() uklanja i vraća zadnji element liste:
>>> l.pop() 5 >>> l [2, 5, 1, 7, 3]Indeks određenog elementa možemo dobiti na sljedeći način:
>>> l [2, 5, 1, 7, 3] >>> l.index(7) 3Ako se u listi nalazi više istih elemenata, vraća se indeks prvog od njih (ovakvo ponašanje moguće je zaobići navođenjem drugog parametra u pozivu metode koji onda označava indeks od kojeg se počinje pretraživati, a ako se izostavi po definiciji je nula i pretražuje se od početka liste):
>>> l.append(7) >>> l [2, 5, 1, 7, 3, 7] >>> l.index(7) 3 >>> l.index(7, 4) 5Broj određenih elemenata u lista možemo dobiti pomoću funkcije count:
>>> l.count(7) 2Pri uklanjanju elemenata liste, osim metode pop() možemo koristiti još dvije metode. Prva od njih služi za uklanjanje elementa iz liste po vrijednosti, a druga uklanja element po njegovom položaju (indeksu) u listi:
>>> l [2, 5, 1, 7, 3, 7] >>> l.remove(3) >>> l [2, 5, 1, 7, 7] >>> del l[2] >>> l [2, 5, 7, 7]I na kraju, dvije vrlo korisne metode su one za sortiranje i okretanje liste. Sortiranje u Pythonu je implementirano Merge sort algoritmom.
>>> l = [5, 2, 1, 4, 3] >>> l.sort() >>> l [1, 2, 3, 4, 5] >>> l.reverse() >>> l [5, 4, 3, 2, 1]Kod rada sa listama u Pythonu treba imati na umu nekoliko stvari. Prva od njih je da se liste se mogu ugnježđivati:
>>> l = [1, 2, 3] >>> l.append ([4, 5]) >>> l [1, 2, 4, [4, 5]]Pojedinom elementu liste možemo pristupiti putem indeksa (numeracija počinje od nule), koji mogu biti i negativni (u slučaju negativnih indeksa numeracija je takva da se numerira od kraja liste; -1 zadnji element liste, -2 predzadnji itd):
>>> l = [1, 2, 3, [4, 5]] >>> l[3] [4, 5] >>> l[3][1] 5 >>> l[-1] [4, 5]Kod višestrukih lista potrebno je obratiti pažnju na način dodavanja elemenata u listu. Moguće je dodati kompletnu listu kao jedan element, a moguće je i dodati elemente liste svakog posebno:
>>> l = [1, 2, 3] >>> l.extend ([4, 5]) >>> l [1, 2, 3, 4, 5] >>> l.append ([4, 5]) [1, 2, 3, 4, 5, [4, 5]]Isti efekt ima i operator konkatenacije (spajanja) lista +:
>>> [1, 3] + [4, 5] [1, 3, 4, 5]Na listama je definiran i operator ponavljanja *:
>>> [1, 2] * 4 [1, 2, 1, 2, 1, 2, 1, 2]Ovdje je važno primjetiti da sljedeći kod ne radi kako bi možda očekivali na prvi pogled
>>> l = [[0, 0], [0, 0]] * 2Iako ovo na prvi pogled izgleda u redu, sljedeće pokazuje u čemu je problem:
>>> l[1][1] = 1 >>> l [[0, 0], [0, 1], [0, 0], [0, 1]]Ovakvo ponašanje u radu sa višestrukim listama je posljedica toga što Python tretira liste kao referencu, tako da operator ponavljanja zapravo stvara kopije referenci a ne vrijednosti samih polja.
Pri pristupanju pojedinim elementima koristili smo indeks. Međutim često se događa želimo koristiti više elemenata liste od jednom. To se postiže korištenjem odrezaka (eng. slices). Sintaksa njihovog korištenja je sljedeća:
ime_liste[početak:kraj]Važno je i napomenuti da je prvi argument uključiv a drugi isključiv te je dozvoljeno ispustiti jedan ili oba argumenta unutar zagrada, pri čemu je onda podrazumjevan početak odnosno kraj liste. Na primjer:
>>> l = [1, 2, 3, 4, 5] >>> l[2:4] [3, 4] >>> l[2:-2] [3] >>> l[:3] [1, 2, 3] >>> l[2:] [3, 4, 5] >>> l[:] [1, 2, 3, 4, 5]Jedan od važnijih načina korištenja lista i notacije lista u Pythonu u kontekstu funkcijskog programiranja je korištenje sljedećeg oblika
[... for ... in ... if ...].Pretpostavimo da imamo listu brojeva koje sve želimo pomnožiti sa dva. Standardni pristup (imperativni) bi uključivao iteriranje svih članova liste i njihovo množenje sa dva. Međutim, funkcijski pristup je mnogo elegantniji:
>>> lista = [2, 5, 1, 3, 6] >>> [x * 2 for x in lista] [4, 10, 2, 6, 12]Ukoliko smo ovo željeli napraviti samo za one članove liste koji su veći od 2, mogli smo koristiti sljedeći izraz:
>>> lista = [2, 5, 1, 3, 6] >>> [x * 2 for x in lista if x > 2]Rezultat ovakve evaluacije je nova lista. Još jedan primjer mogao bi biti inkrement svih elemenata liste:
>>> a = [1, 2, 3] >>> [a + 1 for a in brojevi] [2, 3, 4]
def ime_funkcije (argument1, argument2...): tijelo_funkcijeBitno je napomenuti nekoliko stvari: lista argumenata može biti prazna. Tijelo funkcije ne može biti prazno i mora sadržavati barem jedan izraz (npr. pass - prazna naredba). Argumenti mogu imati i podrazumjevanje vrijednosti, a u tome slučaju moraju se nalaziti na kraju popisa argumenata funkcije . Funkcija vraća vrijednost pomoću ključne riječi return. Slijede primjeri definicije nekih funkcija kao primjer gore navedenih činjenica o funkcijama u Pythonu:
def nop(): pass def zbroji(x, y): return x + y def uvecaj(x, y = 1): return x + yU posljednjem slučaju funkciju je moguće pozvati na dva načina:
>>> uvecaj (4) 5 >>> uvecaj (4, 2) 6
Školski primjer za definiranje rekurzije su faktorijele. Faktorijela se definira induktivno na sljedeći način:
0! = 1 1! = 1 * 0! = 1 2! = 2 * 1! = 2 * 1 3! = 3 * 2! = 3 * 2 * 1Tako bi na primjer bilo:
10! = 10 * 9 * 8 * 7 * 6 * 5 * 4 * 3 * 2 * 1Matematički ovo možemo zapisati na sljedeći način:
n! = n * (n-1) i 0! = 1Odavde sljedi direktna implementacija u Pythonu
def fact (n): if n == 0: return 1 return n * fact(n-1) >>> fact(10) 3628800Još jedan primjer bi mogao biti i provjeravanje da li se određeni element nalazi u listi (ovo je naravno samo akademski primjer, za to se inače koristi ugrađeni Pythonov operator in):
def is_in (lista, x): if (lista == []): return 0 if (x == lista[0]): return 1 return is_in (lista[1:], x)Međutim, treba biti iznimno oprezan kada je rekurzija dobar izbor. Dok bi u gornja dva primjera korištenje rekurzije bilo prihvatljivo, u sljedećem primjeru sa Fibonaccijevim brojevima rekurzija daje katastrofalno spori algoritam eksponencijalne složenosti:
Fibonaccijevi brojevi su definirani rekurzivno na sljedeći način:
F(1) = F(2) = 1 F(n) = F(n-1) + F (n-2) za n > 2U Pythonu to izgleda ovako:
def F(n): if n == 1 or n == 2: return 1 return F(n-1) + F(n-2)Iako ovo na prvi pogleda izgleda korektno (a i je korektno), ovakvo je računanje Fibonaccijevih brojeva iznimno neefikasno. Dok bi se računanjem u npr. for petlji rezultat za veće brojeve dobio gotovo trenutno, rekurzijom bi morali pričekati nekoliko godina (moguće i stoljeća). Probajte na primjer razmisliti koliko bi se puta računao F(5) kod poziva F(50).
lambda popis_varijabli: izrazSljedeći primjer prikazuje definiciju funkcije pomoću lambda izraza:
>>> f = lambda x: x*x >>> fSlijedi i jedan kompliciraniji primjer. Funkciju iz prethodnog poglavlja za inkrement u kojoj smo koristili opcionalne parametre napisat ćemo ponovno sa lambda izrazima:at 0x40365f0c> >>> f(5) 25
def napravi_inkrement_funkciju (n): return lambda x: x + n >>> f = napravi_inkrement_funkciju (10) >>> f(1) 11Lambda izraz može sadržavati i više varijabli. Na primjer, za slučaj tri varijable:
diskriminanta = lambda a, b, c: b * b - 4 * a * cPrava efikasnost lambda izraza dolazi do izražaja kod korištenja funkcija map, reduce i filter koje su opisane u sljedećim poglavljima.
>>> lista = range (10) >>> lista [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> map (lambda x: x*2, lista) [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]Ovdje do izražaja dolazi prava snaga lambda izraza jer bi bilo suvišno i zamorno definirati novu funkciju samo za ovakvu primjenu. Još jedan primjer iz stvarnog svijeta bi bio sljedeći: često se u aplikacijama koje koriste puno matematičkih proračuna koriste lookup tablice. Na primjer, umjesto da se svaki puta koristi FPU za računanje funkcije sinus, možemo prije izvođenja glavnog dijela programa izračunati sve potrebne vrijednosti, a kasnije samo čitati njihove vrijednosti iz liste:
>>> import math >>> sinusi = range (360) >>> sinusi = map (lambda x: math.sin (x), sinusi)Prema prijedlogu novog nacrta programskog jezika Python (Python 3000), ova funkcija će kako stvari sada stoje biti izbačena iz definicije jezika. Razlog tome leži u činjenici da istu funkcionalnost kao
map [funkcija, lista]ima i operacija sa listom:
[funkcija (element) for element in lista]Koji od prethodna dva zapisa je elegantniji i ljepši ostavljam na prosudbu čitatelju - tvorci Pythona su se ionako već odlučili za ovaj drugi. Nova definicija jezika kako stvari sada stoje neće biti usvojena tako brzo, tako da možete koristiti oba zapisa bez bojazni o portabilnosti programa.
>>> lista = range (10) >>> filter (lambda x: x%2, lista) [1, 3, 5, 7, 9]Kao i funkcija map, i ova funkcija će biti izbačena kod sljedeće velike preinake Pythona. Tako smo sljedeći primjer mogli zapisati i na ovaj način:
>>> [x for x in range (10) if x%2] [1, 3, 5, 7, 9]
>>> lista = [2, 5, 1, 3] >>> reduce (lambda x,y: max(x,y), lista) 5Naravno, mogli smo odmah dobiti traženi element sa:
>>> max (lista) 5no to nije smisao gornjega primjera. Sljedeći bi primjer bio "naći zbroj svih elemenata liste":
>>> lista = [2, 5, 1, 3] >>> reduce (lambda x,y : x+y, lista) 11Na ovome primjeru ćemo detaljno pokazati kako reduce radi: lambda izraz će vratiti zbroj parametara koje je primila. Privremena lista koja nastaje tijekom evaluacije reduce() izraza izgleda ovako:
[2, 5, 1, 3] [2, 5, 4] [2, 9] [11]Pošto je 11 jedini preostali element u listi, reduce() gra vraća kao rezultat evaluacije. Važno je napomenuti da sve tri navedene funkcije (map, filter i reduce) ostavljaju listu koja je navedeno kao parametar netaknutim (vraćaju novu listu).
I ovu funkcija će biti izbačena u sljedećoj velikoj preinaci Pythona.
1. izaberi stožerni element u listi 2. podijeli listu na dvije podliste 3. u prvu podlistu stavi sve elemente liste manje od stozernog elementa 4. u drugu podlistu stavi sve elemente liste vece od stozernog elementa 5. Ponavljaj korake 1 do 4 za dobivene podliste sve dok ne dobiješ praznu listuRadi jednostavnosti, pretpostavit ćemo da je stožerni element prvi element liste. Quicksort u Pythonu koristeći funkcijsko programiranje izgleda ovako:
def quicksort (lista): if lista==[]: return [] return qsort([x for x in L[1:] if x< L[0]]) + L[0:1] + \ qsort([x for x in L[1:] if x>=L[0]])Jeste li ikada vidjeli elegantnije i kraće napisani quicksort? :)