2.31. Iteratori i generatori¶
for petlja obavljala je više posla nego što se čini. Ova stranica pokriva protokol iteratora na kojem se izvodi te ključnu riječ yield koja vam omogućuje izgradnju vlastitih iteratora.
2.31.1. Protokol iteratora¶
Svaki objekt koji se može iterirati implementira dvije metode:
__iter__()– vraća iterator nad stavkama objekta.__next__()– na iteratoru, vraća sljedeću stavku ili podižeStopIterationkada ih više nema.
Ugrađena funkcija iter() poziva __iter__; next() poziva __next__. Prođite kroz listu ručno:
it = iter([10, 20, 30])
print(next(it)) # 10
print(next(it)) # 20
print(next(it)) # 30
print(next(it)) # raises StopIteration
for je sažeti zapis za „jednom pozovi __iter__, zatim petlji pozivaj __next__ sve do StopIteration.”¶
Što for x in items: zapravo radi:
_it = iter(items)
while True:
try:
x = next(_it)
except StopIteration:
break
# ... loop body ...
Svaka lista, n-torka, niz znakova, rječnik, skup, objekt datoteke i generator već implementiraju __iter__ i __next__ – zbog čega svi rade s for.
2.31.2. yield i funkcije generatora¶
Funkcija koja sadrži naredbu yield je funkcija generatora. Njezin poziv ne izvodi tijelo; ona vraća objekt generatora (iterator) koji izvodi tijelo jedan yield po jedan:
def count_up_to(n):
i = 0
while i < n:
yield i
i += 1
for value in count_up_to(3):
print(value)
Izlaz:
0
1
2
Svaki poziv funkcije next() nastavlja funkciju do sljedećeg yield, predaje tu vrijednost pozivatelju i zaustavlja se tamo. Lokalno stanje (i u ovom slučaju) čuva se između nastavaka.
next() izvodi tijelo do sljedećeg yield, vraća vrijednost i zaustavlja se. Lokalno stanje preživljava zaustavljanje.¶
Generatori su najlakši način za lijeno (lazy) proizvođenje niza – nikakva lista se ne gradi, stavke se izračunavaju tek kada ih potrošač zatraži, a funkcija može ustupati stavke zauvijek ako želi.
2.31.3. Lijeni cjevovodi (pipelines)¶
Generatori se dobro slažu. Izlaz jednog generatora može hraniti drugi:
def numbers():
i = 0
while True:
yield i
i += 1
def squares(source):
for x in source:
yield x * x
pipeline = squares(numbers())
for v in pipeline:
if v > 100:
break
print(v)
Vrijednosti teku kroz cjevovod jedna po jedna – bez međuliste, bez ugrađene gornje granice u numbers te potrošač (for v in pipeline) odlučuje kada stati.
Svaki next() na potrošaču pokreće jedno povlačenje kroz lanac; vrijednosti postoje samo kada ih nešto zatraži.¶
2.31.3.1. yield from¶
Petlja koja povlači stavke iz drugog iterabilnog objekta i ustupa svaku od njih dovoljno je česta da Python pruža prečac. Izraz yield from iter ustupa svaku vrijednost koju iterabilni objekt proizvede, redom – kao da generator ima ugrađenu for x in iter: yield x petlju:
def chain(*sources):
for source in sources:
yield from source
for v in chain([1, 2, 3], (4, 5), "abc"):
print(v)
Izlaz:
1
2
3
4
5
a
b
c
yield from je potpuno ekvivalentan eksplicitnoj for petlji, samo kraći, i čisto prosljeđuje StopIteration iz unutarnjeg iterabilnog objekta prema vanjskom generatoru – korisno pri ulančavanju nekoliko generatora od kraja do kraja.
2.31.3.2. Kada yield ponestane¶
Izlazak s kraja funkcije generatora (ili nailazak na eksplicitni return) automatski podiže StopIteration. Nema je potrebe podizati ručno; okolna for petlja je vidi i završava.
Koristite generatore kada je proizvodni kôd prirodno napisan kao petlja s nekoliko točaka yield; koristite običnu listnu komprehenziju kada vam je doista potreban cijeli niz u memoriji.