Generaattorit
Eräissä tilanteissa olisi kätevää saada ohjelmassa seuraava alkio (tai useampi alkio) tietystä sarjasta ilman että muodostetaan koko sarjaa kerralla. Pythonissa tämä onnistuu näppärästi generaattoreiden avulla. Generaattorifunktio muistuttaa normaalia arvon palauttavaa funktiota, mutta kun normaalifunktio palauttaa (tai ainakin sen pitäisi palauttaa) samalla syötteellä saman arvon, generaattorifunktio palauttaa seuraavan luvun sarjasta.
Generaattorien toiminta voidaan toteuttaa ohjelmissa myös muilla keinoilla (itse asiassa sama pätee useimpiin ohjelmointitekniikoihin), mutta niiden käyttö selkeyttää ja mahdollisesti säästää muistia tai muita resursseja tietyntyylisissä ohjelmissa.
Avainsana yield
Generaattorifunktion toiminta perustuu avainsanaan yield
. Tarkastellaan esimerkkinä funktiota, joka palauttaa yksi kerrallaan kokonaislukuja nollasta alkaen kunnes maksimiarvo on saavutettu:
def laskuri(maksimi: int):
luku = 0
while luku <= maksimi:
yield luku
luku += 1
Nyt laskurilta voi pyytää seuraavan arvon funktiolla next()
:
if __name__ == "__main__":
luvut = laskuri(10)
print("Eka arvo:")
print(next(luvut))
print("Toka arvo:")
print(next(luvut))
Eka arvo: 0 Toka arvo: 1
Niinkuin esimerkistä huomataan, yield
muistuttaa return
-komentoa siinä, että se palauttaa arvon funktiosta. Eroavaisuus on kuitenkin siinä, että yield palauttaa yksittäisen arvon, ja funktio "muistaa" mihin tilaan se jäi.
Arvoja voi pyytää vain niin kauan kun niitä on generaattorissa jäljellä - tämän jälkeen generaattorifunktio antaa poikkeuksen StopIteration
:
if __name__ == "__main__":
luvut = laskuri(1)
print(next(luvut))
print(next(luvut))
print(next(luvut))
Poikkeuksen voi ottaa kiinni try
-except
lohkolla:
if __name__ == "__main__":
luvut = laskuri(1)
try:
print(next(luvut))
print(next(luvut))
print(next(luvut))
except StopIteration:
print("Luvut loppuivat kesken")
0 1 Luvut loppuivat kesken
Jos halutaan palauttaa kaikki generaattorin tuottamat alkiot, helpointa on iteroida ne läpi for
-lauseella:
if __name__ == "__main__":
luvut = laskuri(5)
for luku in luvut:
print(luku)
0 1 2 3 4 5
Generaattorikoosteet
Generaattorin voi luoda myös listakoostetta (list comprehension) muistuttavalla syntaksilla. Erotuksena listakoosteeseen on, että lauseke ympäröidään kaarisulkeilla hakasulkeiden sijasta.
Esimerkiksi
# Generaattori palauttaa 2:n potensseja
neliot = (x ** 2 for x in range(1, 64))
print(neliot)
for i in range(5):
print(next(neliot))
<generator object <genexpr> at 0x000002B4224EBFC0> 1 4 9 16 25
Toinen esimerkki, jossa generaattori tuottaa kolmimerkkisiä alijonoja englanninkielisistä aakkosista. Esimerkissä tulostetaan generaattorin 10 ensimmäistä alkiota:
alijonot = ("abcdefghijklmnopqrstuvwxyz"[i : i + 3] for i in range(24))
# tulostetaan ensimmäiset 10 alijonoa
for i in range(10):
print(next(alijonot))
abc bcd cde def efg fgh ghi hij ijk jkl