Napredne Python naredbe

Exceptions

Ovde ćemo se ukratko baviti izuzecima (exceptions) koji mogu nastati tokom izvršavanja programa. Najčešće su to greške kojima bi želeli da upravljamo.

Osnovni koncept

Ako pišete program koji će neko drugi da koristi, ne biste želeli da vaš program “krešira” (crash) kada se pojavi neka greška. Recimo, na primer, da vaš program vrši gomilu matematičkih proračuna i da se u enkoj liniji programa nalazi naredba c = a/b. Ako se desi da je u tom trenutku b jednako 0, nastaje deljenje sa nulom i program će tu prekinuti rad sa porukom o grešci. Evo kako to izgleda u sledećemo primeru:

a = 3
b = 0
c = a/b
print('Hi there')

ZeroDivisionError: int division or modulo by zero

Kada se ovakva greška pojavi, nijedna naredba posle naredbe c = a/b neće biti izvršena. U stvari, ako se program ne izvršava u IDLE editoru, korisnik neće ni primetiti da je greška nastala. Program će jednostavno prestati sa radom.

U Python-u, kada se pojavi greška, interpreter generiše izuzetak (exception). Mi možemo programski uhvatiti takav izuzetak i omogućiti programu da nastavi rad. Evo kako se to može postići:

a = 3
b = 0
try:
    c = a/b
except ZeroDivisionError:
    print('Calculation error')
print('Hi there')

Calculation error
Hi There

Različite mogućnosti

Možemo imati više naredbi u try bloku i više except blokova kao u seledećem primeru:

try:
    a = eval(input('Enter a number: '))
    print (3/a)
except NameError:
    print('Please enter a number.')
except ZeroDivisionError:
    print('Can't enter 0.')

Možete izostaviti naziv izuzetka, kao u primeru:

try:
    a = eval(input('Enter a number: '))
    print (3/a)
except:
    print('A problem occurred.')

Generalno govoreći, ne preporučije se ovakva upotreba, međutim, na ovaj nači se “hvataju” svi izuzeci koji se mogu pojaviti u try bloku, pa i oni koje niste predvideli kad ste pisali program. Zato će biti teže da debagirate takav program.

Korišćenje izuzetaka

Kada uhvatite izuzetak, informacija o izuzetku se smešta u Exception objekat. Sledi primer u kojem se korsniku prosleđuje naziv nastalog izuzetka:

try:
    c = a/0
except Exception as e:
    print(e)

int division or modulo by zero

Konstrukcija try/except/else

Možete koristiti i else klauzulu zajedno sa try/except blokovima. Evo primera:

try:
    file = open('filename.txt', 'r')
except IOError:
    print('Could not open file')
else:
    s = file.read()
    print(s)

U ovom primeru, ako fajl filename.txt ne postoji, generiše se izuzetak pod nazivom IOError. U suprotnom, ako fajl postoji, možemo sa njim uraditi neke operacije. Te operacije možemo staviti u else bloku.

Konstrukcije try/finally i with/as

Postoji još jedan blok koji možete koristiti u try/except naredbama. Taj blok se naziva finally. U njemi se nalaze naredbe koje će se izvršavati bez obzira da li se pojavio izuzetak ili ne. Tako, ako se pojavi greška i vaš program “krešira”, finally blok će svejedno biti izvršen. Jedan slučaj korišćenja finally bloka je zatvaranje fajlova, kao u sledećem primeru:

f = open('filename.txt', 'w')
s = 'hi'
try:
    # some code that could potentially fail goes here
finally:
    f.close()

Blok finally može da se koristi i zajedno sa except i else blokovima.

Kada je u pitanju rad sa fajlovima, postoji posebna konstrukcija kojom se obezbeđuje pravilno otvaranje i zatvaranje fajlova. Ovu mogućnost ilustruje sledeći primer:

s = 'hi'
with open('filename.txt') as f:
    print(s, file=f)

Ovo je primer nečega što se naziva menadžer konteksta (context manager). Kontekst menadžei i try/finally konstrukcije se koriste u složenijim aplikacijama kao što su programiranje u računarskoj mreži (network programming).

Više o izuzecima

Postoji jo mnogo toga što može biti urađeno korišćenjem mehanizma izuzetaka. Pogledajte Python dokumentaciju za različite tipove izuzetaka. U stvari, nisu svi izuzeci prouzrokovani greškama. U Python-u postoji posebna naredba raise koju možete da koristite za generisanje vaših posebnih izuzetaka. To je recimo korsino kada pišete svoje klase koje će se korsititi u drugim programima i kada želite da korisnicima klase šaljete poruke o greškama koje oni prave pri koriščenju vaše klase.

Slede primeri korišćenja raise naredbe:

if something:
    raise Exception('My error!')

ili na primer:

if (a < b):
    raise ValueError()

Assertions

Assert naredba je jedan pogodan način da se u programu postave uslovi ispravnosti izvršavanja programa. To se radi u cilju lakšeg debagiranja programa.

Naredba assert ima dva oblika:

assert <uslov>              # gde je uslov neki logički izraz
assert <uslov> , <poruka>   # gde je poruka string koji će biti prikazan

Naredba assert funkcioniše tako da kada je uslov jednak True ništa se ne događa i program nastavlja sa radom, a ako je uslov False program prekida rad uz pojavu greške sa porukom iz stringa poruka.

Evo jednog primera. Recimo da smo donji program smestili u fajl primer.py:

def chkassert(num):
    assert type(num) == int
    return num**2

chkassert('a')

Kada izvršimo gornji program dobićemo poruku:

Traceback (most recent call last):
File "primer.py", line 5, in <module>
    chkassert('a')
File "primer.py", line 2, in chkassert
    assert type(num) == int
AssertionError

Razlog za pojavu greške je što smo u pozivu funkcije chkassert stavili argument ‘a’ (string), pa assert naredba u funkciji proverom tipa pronalazi da tip argumenta nije int i “podiže” grešku.

U slučaju da smo pri pozivu funkcije kao argument stavili neki ceo broj, assert naredba u funkciji chkassert ne bi “podigla” grešku i funkcija bi nam vratila kvadrat tog broja.

Još neke kontrolne naredbe

Naredba continue

Ponekad u for i while petljama imamo potrebu da nastavimo sa petljom bez izvršavanja dela koda iz petlje. Ranije smo videli kako se naredbom break prekida izvršavanje petlje.

Naredba continue omogućava da nastavimo sa izvršavanjem petlje prenošenjem kontrole na početak petlje, to jest preskačući deo koda iz petlje.

Na primer, recimo da želimo da štampamo sve brojeve od 1 do 100 koji nisu deljivi sa 3. To možemo da uradimo na sledeći način:

for x in range(0, 101):
    if x % 3 == 0:
        continue
    print(x)

Ključna reč as

To je naredba kojom se kreira nadimak (alias). Recimo, ako želite da importujete neki modul ali umesto njegovog originalnog imene želite da koristite drugo ime, tada koristite neredbu as kao u sledećem primeru:

import math as matematika
matematika.sin(5)

Naredba with .. as

Naredba with se najčešće koristi za rad sa fajlovima, kao u sledećem primeru:

with open("imefajla.txt","w") as mojFajl:
    mojFajl.write("tekst")

Ključna reč is

Ključna reč is se koristi kada želimo da proverimo da li se dva različita imena varijabli odnose na isti objekat, to jest na istu memorijsku lokaciju. Videli smo da pomoću operatora == možemo da poredimo dva objekta koji mogu da imaju iste vrednosti.

Na primer:

a = [1,2,3]
b = a
print(a is b)

biće štampano True, jer su a i b identični objekti ( proverite sa id(a) i id(b) ), dok će u programu:

a = [1,2,3]
b = [1,2,3]
print(a is b)

biti štampano False, jer su a i b dva različita objekta ( iako sa istim vrednostima ).

Lambda funkcije

Python podržava kreiranje takozvanih anonimnih funkcija, to jest funkcija kojima nije dato ime. Za to se koristi ključna reč lambda kao u sledećem primeru:

def f(x):
    return x**2
g = lambda x: x**2
print (f(8),g(8))

Funkcija f(x) je kreirana na klasičan način, dok je funkcija g kreiranja korišćenjem lambda konstruktora.

Funkcije kreirane lambda konstruktorom nemaju return naredbu, jer uvek vraćaju vrednost izraza kojim su definisane.

Lambda funkcija može da se nađe u bilo kojoj Python naredbi na mestu gde se očekuje funkcionalni poziv. Najčešća primena lambda funkcija je u filter, map i reduce operacijama, kao u sledećim primerima.

* Filter *

Funkcija filter() kao parametre ima neku drugu funkciju i listu.

Ova druga funkcija se poziva za sve elemente liste, a filter vraća novu listu od onih elemenata originalne liste ya koje druga funkcija vraća True.

Evo primera:

# Program to filter out only the even items from a list
my_list = [1, 5, 4, 6, 8, 11, 3, 12]
new_list = list(filter(lambda x: (x%2 == 0) , my_list))
# Output: [4, 6, 8, 12]
print(new_list)

* map *

Funkcija map() ima za parametre drugu funkciju i listu.

Druga funkcija se poziva za sve elemente liste, a map vraća listu koja se dobija od vrednosti koje vraća druga funkcija.

Evo primera:

# Program to double each item in a list using map()
my_list = [1, 5, 4, 6, 8, 11, 3, 12]
new_list = list(map(lambda x: x * 2 , my_list))
# Output: [2, 10, 8, 12, 16, 22, 6, 24]
print(new_list)

* reduce *

Funkcija reduce ima dva argumenta: funkciju i listu. Funkcija reduce nije built-in funkcija u Python 3, već se nalazi u modulu functtools iz standardne biblioteke. Ova funkcija, za razliku od filter i map funkcija ne može se lako zameniti sa list comprehesion tehnikom.

Da bi razumeli kako reduce fukcioniše najbolje je da pogledamo jedan primer u kojem sabiramo sve brojeve od 1 do 100:

total = 0
for i in range(1,101):
    total = total + i

Funkcija reduce se može iskoristiti da se gornji algoritam izrazi u samo jednoj liniji:

total = reduce(lambda x,y: x+y, range(1,101))

Generalno govoreći funkcija kao drugi argument ima neki iterabilni objekata, pa primenjuje funkciju datu u prvom argumentu na sve elemente iz iterabilnog argumenta, pri čemu akumulira rezultat u svakom koraku.

Evo još jednog primera u kojem se korišćenjem reduce funkcije izračunava faktorijel:

def fact(n):
        return reduce(lambda x,y:x*y, range(1,n+1))