Een echte generator stapsgewijs waarden laten leveren.
Met bronwaarnemingen bewijzen dat werk wordt uitgesteld.
Stoppen, uitputting en afsluiting met finally onderscheiden.
Je wilt meetwaarden verwerken zonder eerst een volledige resultatenlijst te bouwen. Soms heeft de aanroeper maar één of twee uitkomsten nodig. Een generator laat de verwerking stap voor stap plaatsvinden wanneer de volgende waarde wordt gevraagd.
Een functie tijdelijk onderbreken
Een generatorfunctie bevat yield. Bij het aanroepen maakt Python een generatorobject; de body wordt dan nog niet vanaf het begin uitgevoerd. De eerste next start de body en loopt tot een waarde met yield wordt geleverd.
Een volgende next gaat verder na die onderbreking. Lokale variabelen en de positie in de functie blijven tussendoor bewaard. De generator is daarmee een iterator: hij heeft voortgang en kan uitgeput raken.
Yield onderbreekt de body en close bereikt finally
Het logboek in het voorbeeld maakt de volgorde zichtbaar. Alleen een generatorobject maken voert de body nog niet uit. Na één opgevraagde uitkomst zie je alleen de handelingen vóór die eerste onderbreking.
Lui betekent uitgesteld, niet automatisch sneller
Deze werkwijze heet luie evaluatie: werk gebeurt pas wanneer het resultaat nodig is. Dat kan voorkomen dat ongebruikte waarden worden berekend of bewaard. Als je uiteindelijk alles nodig hebt, blijft al dat werk wel nodig.
Een omzetting met list(generator) vraagt alle resterende waarden op en maakt alsnog een lijst. Een reeds verbruikte waarde verschijnt daarbij niet opnieuw. Ook een tweede list op een volledig uitgeputte generator blijft leeg.
Niet verder lezen dan nodig
Bij een generator hoort ook de vraag wanneer de bron wordt aangesproken. De opdracht double_values mag de volgende bronwaarde pas vragen wanneer de aanroeper een volgende uitkomst vraagt. Een verborgen list(source) aan het begin zou de hele bron te vroeg verbruiken.
De controles gebruiken daarom naast uitkomsten een echt spoor van bronhandelingen. Een juiste eindlijst alleen is niet voldoende bewijs voor de afgesproken luiheid. Twee implementaties kunnen dezelfde waarden opleveren maar hun werk op verschillende momenten doen.
positive_squares slaat straks niet-positieve waarden over. Voor één uitkomst mag die functie dus meerdere bronwaarden nodig hebben, maar zij leest niet alvast voorbij de eerstvolgende positieve waarde.
Een grens tijdens de doorloop toepassen
take_until_negative levert waarden zolang ze niet negatief zijn. De eerste negatieve waarde wordt gelezen om te weten dat de reeks moet stoppen, maar niet meer geleverd. Waarden daarna mogen niet meer uit de bron worden opgevraagd.
Het voorbeeld gebruikt een lege tekst als andere brongrens. Hetzelfde principe bepaalt wanneer je de doorloop beëindigt.
Na de eerste lege tekst blijft de rest ongelezen
def before_empty(source):
for word in source:
if word == "":
return
yield word.upper()
def observed_words(events):
for word in ["a", "b", "", "later"]:
events.append(word)
yield word
events = []
stream = before_empty(observed_words(events))
print(events)
print(list(stream))
print(events)
Verwachte uitvoer
[]
['A', 'B']
['a', 'b', '']
Dit maakt de stopregel concreter dan achteraf een volledige lijst filteren. Een filter zou ook latere positieve waarden kunnen teruggeven. Volgens deze afspraak beëindigt het eerste negatieve getal juist de hele doorloop.
Veelgemaakte fout: een lijst teruggeven
Een functie die een lijst opbouwt en die met return teruggeeft, kan dezelfde getallen bevatten. Zij voert haar berekeningen echter al tijdens de functieaanroep uit. Zij voldoet daarom niet aan een opdracht die een echte, luie generator vraagt.
yield value levert één waarde en onderbreekt de functie. return zonder waarde beëindigt een generator juist. Schrijf in een generator geen losse raise StopIteration als gewone stopregel; gebruik return of laat de body eindigen.
Uitputting en afsluiten
Wanneer een generator klaar is, meldt next de uitputting met StopIteration. Een lege bron kan direct klaar zijn zonder ooit een waarde te leveren. De aanroeper hoeft daarvoor geen speciale eindwaarde uit de reeks te halen.
Een generator kan ook met close() worden afgesloten. Daarna levert hij geen nieuwe waarden meer. Een finally rond het gedeelte met yield wordt bij zo'n afsluiting uitgevoerd, zoals in het eerste voorbeeld.
In closeable_values registreert dat blok eenmaal closed, ook bij natuurlijke uitputting of een bronfout. Een nooit gestarte generator is een aparte grens: close() start zijn body niet en bereikt dan ook dat finally-blok niet. In de volgende les verbind je opruiming aan een expliciet with-blok.
AAN DE SLAG 0/4
1
Eén bronwaarde per gevraagde uitkomst verwerken
Maak double_values(source) als echte generator. Lever iedere ontvangen int verdubbeld in volgorde. Maak bij de functieaanroep nog geen iterator en lees nog niets; iedere volgende uitkomst vraagt pas de nodige volgende bronwaarde. Lege invoer is direct uitgeput.
2
Lui filteren tot de eerstvolgende bruikbare waarde
Maak positive_squares(source) als echte generator. Sla ints <= 0 over en lever van iedere positieve int het kwadraat. Doe niets vóór de eerste next en lees per gevraagde uitkomst slechts tot de volgende positieve waarde.
3
De bron bij de eerste negatieve grens stoppen
Maak take_until_negative(source) als echte generator. Lever ontvangen ints zolang ze niet negatief zijn. Lees de eerste negatieve waarde om te stoppen, lever haar niet en lees daarna niets meer uit de bron. Een lege bron levert niets.
4
Opruimen bij uitputting, close en bronfouten
De starter levert source al met yield from. Maak er een generator met try/finally van die de ontvangen waarden ongewijzigd levert en eenmaal "closed" aan events toevoegt bij natuurlijke uitputting, bronfout of close na starten. Vóór de eerste next gebeurt niets; close op een nooit gestarte generator voegt dus ook niets toe.
Onthoud dit
yield bewaart de positie en lokale toestand tussen twee opvragingen.
Luiheid gaat ook over wanneer de bron werkelijk wordt gelezen.
Een stopregel kan de doorloop beëindigen zonder latere waarden op te vragen.
Hierna maak je afspraken over openen en opruimen met een contextmanager.
Je hoeft het niet in één keer goed te doen. Na het uitvoeren zie je hier welke stappen al kloppen.
1Eén bronwaarde per gevraagde uitkomst verwerken2Lui filteren tot de eerstvolgende bruikbare waarde3De bron bij de eerste negatieve grens stoppen4Opruimen bij uitputting, close en bronfouten