2.31. Iteraattorit ja generaattorit¶
for -silmukka on tehnyt enemmän työtä kuin miltä näyttää. Tämä sivu käsittelee iteraattoriprotokollaa, jonka varassa se toimii, sekä yield -avainsanaa, jonka avulla voit rakentaa omia iteraattoreitasi.
2.31.1. Iteraattoriprotokolla¶
Jokainen olio, jonka yli voidaan iteroida, toteuttaa kaksi metodia:
__iter__()– palauttaa iteraattorin olion alkioiden yli.__next__()– iteraattorissa palauttaa seuraavan alkion tai nostaa poikkeuksenStopIteration, kun niitä ei ole enää.
Sisäänrakennettu iter() kutsuu metodia __iter__; next() kutsuu metodia __next__. Käy listaa läpi käsin:
it = iter([10, 20, 30])
print(next(it)) # 10
print(next(it)) # 20
print(next(it)) # 30
print(next(it)) # raises StopIteration
for on syntaksisokeria ilmaisulle ”kutsu __iter__ kerran, sitten silmukoi metodia __next__, kunnes StopIteration”.¶
Mitä for x in items: todellisuudessa tekee:
_it = iter(items)
while True:
try:
x = next(_it)
except StopIteration:
break
# ... loop body ...
Jokainen lista, monikko, merkkijono, sanakirja, joukko, tiedosto-olio ja generaattori toteuttaa jo metodit __iter__ ja __next__ – minkä vuoksi ne kaikki toimivat for -silmukan kanssa.
2.31.2. yield ja generaattorifunktiot¶
Funktio, joka sisältää yield -lauseen, on generaattorifunktio. Sen kutsuminen ei suorita runkoa; se palauttaa generaattoriolion (iteraattorin), joka suorittaa rungon yhden yield -kohdan kerrallaan:
def count_up_to(n):
i = 0
while i < n:
yield i
i += 1
for value in count_up_to(3):
print(value)
Tuloste:
0
1
2
Jokainen kutsu funktioon next() jatkaa funktiota seuraavaan yield -kohtaan asti, luovuttaa tuon arvon kutsujalle ja keskeytyy siihen. Paikallinen tila (tässä tapauksessa i) säilyy jatkamisten välillä.
next() suorittaa rungon seuraavaan yield -kohtaan asti, luovuttaa arvon takaisin ja keskeytyy. Paikallinen tila säilyy keskeytyksen yli.¶
Generaattorit ovat helpoin tapa tuottaa sekvenssi laiskasti – listaa ei rakenneta, alkiot lasketaan vain kun kuluttaja niitä pyytää, ja funktio voi tuottaa alkioita loputtomiin niin halutessaan.
2.31.3. Laiskat liukuhihnat¶
Generaattorit kytkeytyvät hyvin yhteen. Yhden generaattorin tuloste voi syöttää toista:
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)
Arvot virtaavat liukuhihnan läpi yksi kerrallaan – ei välivaiheen listaa, ei muuttujaan numbers sisäänrakennettua ylärajaa, ja kuluttaja (for v in pipeline) päättää milloin lopettaa.
Jokainen next() kuluttajassa laukaisee yhden vedon ketjun läpi; arvot ovat olemassa vain kun jokin niitä pyytää.¶
2.31.3.1. yield from¶
Silmukka, joka vetää alkioita toisesta iteroitavasta ja tuottaa jokaisen niistä, on riittävän yleinen, että Python tarjoaa sille oikotien. Lauseke yield from iter tuottaa jokaisen arvon, jonka iteroitava tuottaa, järjestyksessä – aivan kuin generaattorissa olisi sisäänkirjoitettu for x in iter: yield x -silmukka:
def chain(*sources):
for source in sources:
yield from source
for v in chain([1, 2, 3], (4, 5), "abc"):
print(v)
Tuloste:
1
2
3
4
5
a
b
c
yield from on täysin samanarvoinen kuin eksplisiittinen for -silmukka, vain lyhyempi, ja se välittää poikkeuksen StopIteration sisemmästä iteroitavasta ulompaan generaattoriin siististi – hyödyllistä ketjutettaessa useita generaattoreita peräkkäin.
2.31.3.2. Kun yield loppuu¶
Generaattorifunktion lopusta putoaminen (tai eksplisiittiseen return -lauseeseen osuminen) nostaa poikkeuksen StopIteration automaattisesti. Sitä ei tarvitse nostaa käsin; ympäröivä for -silmukka näkee sen ja päättyy.
Käytä generaattoreita, kun tuottava koodi kirjoitetaan luontevasti silmukkana, jossa on muutama yield-kohta; käytä tavallista listamuodostinta, kun todella tarvitset koko sekvenssin muistissa.