Iteratie als interface · oefening
Pauzeer werk met yield
Les 3 had twintig regels en twee klassen nodig om een lijst te doorlopen. Hier is hetzelfde:
yield in plaats van return, en dat ene woord verandert een gewone functie in iets dat het volledige iteratorprotocol voor je oplevert.
Wat yield doet
Een functie met yield is een generatorfunctie. Die aanroepen voert de functie-inhoud niet uit:
De aanroep geeft een generatorobject terug en dat object is een iterator: het heeft __iter__ en __next__, kan worden doorgegeven aan for, list, sum en is na één ronde uitgeput.

De functie-inhoud wordt pas uitgevoerd als iets om een item vraagt. Dan draait die tot de volgende yield en stopt daar. In het diagram betekent ‘Generator function’ generatorfunctie en ‘calling it creates a generator’ dat aanroepen een generator maakt. ‘State is kept between calls’ zegt dat de toestand tussen aanroepen bewaard blijft. ‘Paused’ betekent gepauzeerd, ‘at yield’ bij yield en ‘resume here until the next yield’ hier verdergaan tot de volgende yield:
Lees die uitvoer zorgvuldig. “starting” wordt pas bij de eerste next() afgedrukt. “between” wordt tijdens de tweede afgedrukt. De functie draait niet om daarna terug te keren; die pauzeert, bewaart alles wat die had en gaat verder waar die bleef.
Als de functie eindigt, gooit de generator zelf StopIteration op. Dat schrijf je nooit zelf.
Wat gebeurt er als je een generatorfunctie aanroept?
Lokale toestand zonder extra werk bewaren
De pauze bewaart je lokale variabelen en daarom vervangt dit het mechanische werk uit les 3. De positie die je in self._index moest opslaan is nu gewoon een plek in de code:
total overleeft pauzes zonder ergens een attribuut van te zijn. Vergelijk dat met een RunningTotalIterator schrijven met __init__, __iter__, __next__, een opgeslagen index en een opgeslagen totaal. Dan heb je het argument voor generators in één voorbeeld.
Itereren zonder onderliggende lijst
Een generator heeft geen collectie nodig om te doorlopen. Die kan waarden opleveren die nooit allemaal tegelijk bestaan:
Er is nergens een lijst met labels. Elk label wordt gemaakt als erom wordt gevraagd en na gebruik weggegooid. Dat is het idee van ‘dingen die te groot zijn om te bewaren’ uit les 1, nu als iets wat je kunt schrijven.
Een klasse iterable maken met yield
__iter__ mag een generatorfunctie zijn. Dat is de netste manier om iets te doorlopen dat niet alleen een opgeslagen lijst is:
Nog steeds opnieuw doorloopbaar, omdat elke aanroep van __iter__ een nieuwe generator met een eigen positie start. Als __iter__ moet filteren, omzetten of afvlakken, schrijf je dit. Als die alleen een opgeslagen collectie doorgeeft, is return iter(self._questions) uit les 4 korter en zegt die dat ook.
Eén aandachtspunt
Een generator raakt uitgeput zoals elke iterator en de fout is makkelijk gemaakt:
De tweede sum krijgt niets. Als je de waarden twee keer nodig hebt, bouw dan één keer een lijst en hergebruik die. Les 6 gaat over kiezen welke resultaatvorm je wilt en hoe je een luie bron op een exacte grens stopt.
Opdracht
Implementeer één generator: passing(records, mark) levert elke registratie op waarvan percentage minstens mark is, in de invoervolgorde.
passing aanroepen mag nog geen registratie inspecteren. Die moet pas beginnen als de aanroeper om een resultaat vraagt en lui genoeg blijven zodat een aanroeper één geslaagde registratie uit een eindeloze invoer kan nemen.
De gegeven functies running_total en numbered demonstreren opgebouwde toestand en omzetting. De gegeven QuestionBank.__iter__ demonstreert een opnieuw doorloopbare generatormethode. Lees ze en voer ze uit, maar laat ze ongewijzigd.