2.31. Iteratoare și generatoare¶
Bucla for a făcut mai multă muncă decât pare. Această pagină acoperă protocolul de iterare pe care se bazează și cuvântul-cheie yield care îți permite să-ți construiești propriile iteratoare.
2.31.1. Protocolul de iterare¶
Fiecare obiect care poate fi parcurs în buclă implementează două metode:
__iter__()– întoarce un iterator peste elementele obiectului.__next__()– pe iterator, întoarce elementul următor sau ridicăStopIterationcând nu mai există altele.
Funcția încorporată iter() apelează __iter__; next() apelează __next__. Parcurge o listă manual:
it = iter([10, 20, 30])
print(next(it)) # 10
print(next(it)) # 20
print(next(it)) # 30
print(next(it)) # raises StopIteration
for este o prescurtare pentru „apelează __iter__ o dată, apoi iterează pe __next__ până la StopIteration.”¶
Ce face for x in items: de fapt:
_it = iter(items)
while True:
try:
x = next(_it)
except StopIteration:
break
# ... loop body ...
Fiecare listă, tuplu, șir, dicționar, set, obiect fișier și generator implementează deja __iter__ și __next__ – de aceea toate funcționează cu for.
2.31.2. yield și funcțiile generatoare¶
O funcție care conține o instrucțiune yield este o funcție generatoare. Apelarea ei nu execută corpul; întoarce un obiect generator (un iterator) care rulează corpul câte un yield o dată:
def count_up_to(n):
i = 0
while i < n:
yield i
i += 1
for value in count_up_to(3):
print(value)
Rezultat:
0
1
2
Fiecare apel la next() reia funcția până la următorul yield, transmite acea valoare apelantului și se întrerupe acolo. Starea locală (i în acest caz) este păstrată între reluări.
next() rulează corpul până la următorul yield, transmite valoarea înapoi și se întrerupe. Starea locală supraviețuiește întreruperii.¶
Generatoarele sunt cel mai simplu mod de a produce o secvență în mod leneș – nu se construiește nicio listă, elementele sunt calculate doar când consumatorul le cere, iar funcția poate produce elemente la nesfârșit dacă dorește.
2.31.3. Conducte leneșe¶
Generatoarele se compun bine. Rezultatul unui generator poate alimenta altul:
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)
Valorile curg prin conductă câte una – fără listă intermediară, fără limită superioară încorporată în numbers, iar consumatorul (for v in pipeline) decide când să se oprească.
Fiecare next() pe consumator declanșează o extragere prin lanț; valorile există doar când ceva le cere.¶
2.31.3.1. yield from¶
O buclă care extrage elemente dintr-un alt iterabil și produce fiecare element este suficient de frecventă încât Python oferă o scurtătură. Expresia yield from iter produce fiecare valoare pe care iterabilul o generează, în ordine – ca și cum generatorul ar avea o buclă for x in iter: yield x inclusă:
def chain(*sources):
for source in sources:
yield from source
for v in chain([1, 2, 3], (4, 5), "abc"):
print(v)
Rezultat:
1
2
3
4
5
a
b
c
yield from este exact echivalent cu bucla for explicită, doar mai scurt, și propagă curat StopIteration de la iterabilul interior către generatorul exterior – util la înlănțuirea mai multor generatoare cap la cap.
2.31.3.2. Când yield se epuizează¶
Ajungerea la finalul unei funcții generatoare (sau întâlnirea unui return explicit) ridică automat StopIteration. Nu este nevoie să o ridici manual; bucla for din jur o detectează și se încheie.
Folosește generatoare când codul producător este scris în mod natural ca o buclă cu câteva puncte yield; folosește o simplă comprehensiune de listă când chiar ai nevoie de întreaga secvență în memorie.