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.