Napredne tehnike

Iteratori

Sigurno ste primetili da većina “kontejnerskih” objekata mogu da se vrte u for petljama:

for element in [1, 2, 3]:
    print(element)
for element in (1, 2, 3):
    print(element)
for key in {'one':1, 'two':2}:
    print(key)
for char in "123":
    print(char)
for line in open("myfile.txt"):
    print(line, end='')

Ovaj način pristupa je jasan, koncizan i pogodan. Korišćenje iteratora je jedna od dobrih karakteristika Pythona. Iza scene for naredba ustvari poziva funkciju __iter__ primenjenu na kontejner objektu. Funkcija vraća iterator objekat u kojem postoji metoda ~iterator.__next__ kojom se pristupa elementima iz kontejnera, element po element. Kada više nema elemenata, metoda ~iterator.__next__ podiže izuzetak StopIteration koji kazuje for petlji da završi rad. Metod ~iterator.__next__ može se pozvati ugrađenom funkcijom next. Sledeći primer pokazuje kako to funkcioniše:

>>> s = 'abc'
>>> it = iter(s)
>>> it
<iterator object at 0x00A1DB50>
>>> next(it)
'a'
>>> next(it)
'b'
>>> next(it)
'c'
>>> next(it)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
    next(it)
StopIteration

Pošto smo videli mehanizam koji stoji iza iterator protokola, sada je lako da takav mehanizam dodamo načoj klasi. Definisaćemo __iter__ metod koji vraća jedan objekat sa ~iterator.__next__ metodom. Ako klasa definiše metod __next__ tada metod __iter__ vraća samo self.

class Reverse:
    """Iterator for looping over a sequence backwards."""
    def __init__(self, data):
        self.data = data
        self.index = len(data)
    def __iter__(self):
        return self
    def __next__(self):
        if self.index == 0:
            raise StopIteration
        self.index = self.index - 1
        return self.data[self.index]
>>> rev = Reverse('spam')
>>> iter(rev)
<__main__.Reverse object at 0x00A1DB50>
>>> for char in rev:
...     print(char)
...
m
a
p
s

Generatori

Generatori su prost i moćan alat za kreiranje iteratora. Oni se pišu kao obične funkcije, ali koriste yield naredbu kad god treba da vrate podatak. Svaki put kada se pozove next funkcija generator nastavlja tamo gde je stao u prethodnom pozivu ( on pamti sve podatke i poslednju izvršenu naredbu). Sledeći primer pokazuje kako je jednostavno kreiranje generatora:

def reverse(data):
    for index in range(len(data)-1, -1, -1):
        yield data[index]
>>> for char in reverse('golf'):
...     print(char)
...
f
l
o
g

Sve što može biti urađeno sa generatorima, može biti urađeno i sa klasama u kojima su generisani iteratori (na način kako smo videli gore). Ono što generatore čini tako kompaktnim je činjenica da se metode __iter__ i ~generator.__next__ kreiraju automatski.

Druga osobina je da se lokalne varijable i stanje izvršavanja automatski pamte između poziva. Ovo čini da je pravljenje funkcije jednostavnije nego u primeru sa iteratorom.

Na kraju da pomenemo da pored toga što pamte podatek i stanje programa, generatori na kraju automatski generišu izuzetak StopIteration.

Kombinacijom prethodnih osobina generatora postignuto je da pisanje generatora nije ništa složenije od pisanja običnih funkcija

Generatorski izrazi

Neki prosti generatori mogu biti kodirani kao izrazi koristeći sintaksu sličnu u “list comprehension”, ali sa malim umesto srednjih zagrada.

Ovakvi izrazi su pogodni u situacijama kada se generator koristi neposredno u nekoj funkciji (kao argument). Generatorski izrazi su kompaktniji, ali manje izražajni od punih generatora, ali su i memorijski manje zahtevni od “list comprehensen” izraza.

Primeri:

>>> sum(i*i for i in range(10))                 # sum of squares
285

>>> xvec = [10, 20, 30]
>>> yvec = [7, 5, 3]
>>> sum(x*y for x,y in zip(xvec, yvec))         # dot product
260

>>> unique_words = set(word for line in page  for word in line.split())

>>> valedictorian = max((student.gpa, student.name) for student in graduates)

>>> data = 'golf'
>>> list(data[i] for i in range(len(data)-1, -1, -1))
['f', 'l', 'o', 'g']

Dekoratori

Po definiciji, dekorator je funkcija koja kao argument ima drugu funkciju i proširuje (menja) njeno ponašanje a da pri tome ne menja samu tu funkciju.

Pojam dekoratora pojasnićemo korišćenjem sledećeg primera:

def mydecorator(f):
    def wrapped(*args, **kwargs):
        print "Before decorated function"
        r = f(*args, **kwargs)
        print "After decorated function"
        return r
    return wrapped

@mydecorator
def myfunc(myarg):
    print "my function", myarg
    return "return value"

r = myfunc('asdf')
print r

Dekorator sa argumentima

Dekorator ima samo jedan argument (funkciju koju dekoriše), zato će vam trebati “fabrika” za kreiranje dekoratora za dekorator sa argumentima. Za razliku od prethodnog primera, uočite kako se sada poziva funkcija sa zagradama @mydecorator_not_actually(count=5) koja proizvodi stvarni dekorator:

def mydecorator_2(count):
    def true_decorator(f):
        def wrapped(*args, **kwargs):
            for i in range(count):
                print ("Before decorated function")
            r = f(*args, **kwargs)
            for i in range(count):
                print ("After decorated function")
            return r
        return wrapped
    return true_decorator

@mydecorator_2(count=5)
def myfunc(myarg):
    print ("my function", myarg)
    return "return value"

r = myfunc('asdf')
print (r)

Dobar tutorijal u vezi sa dekoratorima možete da pogledate ako kliknete Dekoratori.

Meta klase

Meta klase u Pythonu predstavljuju klase koju proizvode klase. Jedna takva klasa je “type” klasa (klasa svih klasa). Meta klase se retko koriste u praksi. Detalnjiji opis meta kalsa možete da nađete ako kliknete Meta klase

Zadaci

  1. Napisati iretator klasu Neparni koja daje (metodom next) sve neparne prirodne brojeve.
  2. Napisati genarator funkciju koja radi isto što i klasa Neparni.
  3. Napisati timing dekorator kojim se određuje vreme trajanja izvršavanja funkcije.
  4. Napisiste dekorator kojim se prebrojava kolko je puta pozvana funkcija koja se dekoriše.
  5. Primenite oba gornja dekoratora da dobijete i vreme izvršavanja i broj poziva dekorisane funkcije.