Generatori/iteratori

Generatorske funkcije

Spomenuli smo da for petlja može “ići” ne samo po elementima liste (ili drugih sequence tipova) nego po bilo kojem iteratoru. Iteratori su objekti koji definiraju niz omogućavanjem dohvaćanja sljedećeg elementa iz niza. Time se definira niz jer se ponavljanjem te radnje prođu tj. dohvate svi elementi niza. Iterator dakle pamti koliko elemenata je već prošao te sadrži recept kako dobiti sljedeći element. Pisanje općenitog iteratora obično nije praktično. Jednostavnije je koristiti generatorske funkcije. Za razliku od običnih funkcija koje vraćaju jednu vrijednost (koja može biti lista ili tuple) generatorske funkcije vraćaju više vrijednosti koristeći princip iteratora. Sama generatorska funkcija izgleda isto kao obična osim što u sebi sadrži naredbu yield. yield dohvaća sljedeću od više vrijednosti koje generatorska funkcija vraća. Ona je slična naredbi return jer kao i return izlazi van funkcije i vrati vrijednost. Međutim, kad se zatraži sljedeća vrijednost, generatorske funkcije će se nastaviti izvršavati dalje (a ne od početka) do sljedeće naredbe yield. Pogledajmo primjere:

def f():
  print("prije prvog yielda")
  yield 1
  print("poslije prvog yielda")
  yield 2
  print("poslije drugog yielda")
  yield 3
  print("poslije trećeg yielda")
  return 4

i = f()
print(i)
print(1)
print('next(i) daje', next(i))
print(2)
print('next(i) daje', next(i))
print(3)
print('next(i) daje', next(i))
try:
  print(4)
  print('next(i) daje', next(i))
except StopIteration as e:
  print('return je vratio',e.value)
<generator object f at 0x106ec9480>
1
prije prvog yielda
next(i) daje 1
2
poslije prvog yielda
next(i) daje 2
3
poslije drugog yielda
next(i) daje 3
4
poslije trećeg yielda
return je vratio 4

Nakon što pozivatelj zatraži sljedeću vrijednost funkcija se izvršava do naredbe yield. Kad pozivatelj ponovno zatraži sljedeću vrijednost, izvršavanje funkcije se nastavlja od naredbe koja slijedi prethodnoj naredbi yield do sljedeće yield. Ako pozivatelj zatraži sljedeću vrijednost a izvršavanje funkcije dođe do kraja ili do naredbe return to izazove grešku StopIteration. Ta greška služi kao oznaka pozivatelju da nema više vrijednosti koje funkcija treba vratiti.

Obično je pozivatelj petlja:

def f():
  print("prije prvog yielda")
  yield 1
  print("poslije prvog yielda")
  yield 2
  print("poslije drugog yielda")
  yield 3
  print("poslije trećeg yielda")
  return 4

for x in f():
  print('u petlji',x)
  print('.')
prije prvog yielda
u petlji 1
.
poslije prvog yielda
u petlji 2
.
poslije drugog yielda
u petlji 3
.
poslije trećeg yielda

U gornjem primjeru petlja interno obrađuje grešku tako da nam ovdje detalji obrade pogrešaka nisu bitni. Nešto više detalja o try/except je dano u Zadaća 8 (try ... except ... else, raise).

Generator comprehensions

Ako se for .. in stavi u uglate zagrade rezultat je lista.

i = [x+100 for x in range(0,3)]
print(i)
[100, 101, 102]

Ako se for .. in stavi u okrugle zagrade rezultat je iterator.

i = (x+100 for x in range(0,3))
print(i)
print('next(i) daje', next(i))
print('next(i) daje', next(i))
print('next(i) daje', next(i))
<generator object <genexpr> at 0x10d8d5390>
next(i) daje 100
next(i) daje 101
next(i) daje 102

Pogledati primjere u odjeljku Zadatak 3b.

Više na:

Pretvaranje liste u iterator

Koristeći iter() možemo prebaciti razne tipove u iterator npr. liste:

i = iter([1,2,3])
print(i)
print('next(i) daje', next(i))
print('next(i) daje', next(i))
print('next(i) daje', next(i))
<list_iterator object at 0x104ea4978>
next(i) daje 1
next(i) daje 2
next(i) daje 3

Pretvaranje iteratora u listu

Koristeći list() možemo prebaciti razne tipove u listu npr. iteratore:

def f():
  yield 1
  yield 2
  yield 3

print(list(f()))
[1, 2, 3]

Pridruživanje iteratora varijablama

Može biti koristan i ovaj način pozivanja generatorskih funkcija:

def f():
  yield 1
  yield 2
  yield 3

a, b, c = f()
print(a,b,c)
1 2 3