0%

Iteratie als interface · oefening

Begrijp het iteratorprotocol

iter() en next() vormen de kant van de aanroeper. Deze les gaat over de andere kant: wat een object moet bieden om überhaupt te kunnen worden doorlopen.

Het zijn twee methoden en je kent de naamconventie van beide.

De twee methoden

__iter__ geeft een iterator terug. Python roept die aan als iets om een iterator vraagt, wat iter() en elke for-lus doen.

__next__ geeft het volgende item terug of gooit StopIteration op als er geen items meer zijn. Python roept die aan als iets om een item vraagt, wat next() doet.

Een iterable definieert __iter__. Een iterator definieert beide en de eigen __iter__ geeft zichzelf terug.

Dit is het kleinste volledige voorbeeld:

Try it

Nergens een lijst. De getallen bestaan pas als __next__ ze maakt en de for-lus weet dat niet en geeft er ook niet om.

Het rustig lezen

__iter__ geeft self terug, wat de definitie van een iterator is: die is de doorloper, dus om een doorloper vragen geeft zichzelf terug. Dat is dezelfde iter(x) is x die je in les 1 testte.

__next__ doet het werk. Die controleert of er nog iets over is, gooit anders StopIteration op, verplaatst de positie en geeft het item terug. Elke ooit geschreven iterator heeft die vorm.

Let op wat er bij een tweede ronde gebeurt:

Try it

Uitgeput, precies zoals de lijstiterator in les 1. Countdown bewaart de positie in self.current en niets zet die terug. Het is een iterator en dus voor eenmalig gebruik.

Wat moet __next__ doen als er niets meer over is?

Iterable en iterator als afzonderlijke objecten

Countdown is beide tegelijk en raakt daarom uitgeput. Als een object opnieuw doorloopbaar moet zijn, houd de twee dan gescheiden: de iterable maakt elke keer een nieuwe iterator.

Try it

Beide rondes zien beide items, omdat elke list()-aanroep een nieuwe BankWalker met een eigen _index kreeg. De bank bewaart de gegevens; de doorloper bewaart de positie. Dat is precies de opzet van een lijst met de eigen iterator en nu zie je waarom.

Schrijf dit niet

Twee klassen en ongeveer twintig regels om een lijst te doorlopen die je al hebt. In echte code bijna nooit.

Deze les bestaat om het protocol concreet te maken in plaats van alleen een beschrijving. De volgende twee lessen laten zien wat je in de praktijk schrijft: __iter__ die delegeert aan iets dat al iterable is en generators die deze hele vorm uit een gewone functie maken.

De oefening bouwt één volledige iterator met de hand, één keer.

Opdracht

Schrijf het protocol één keer met de hand, zodat het geen beschrijving meer is.

Countdown(start) is een iterator die start, start - 1, tot en met 1 oplevert en daarna stopt. Die definieert beide methoden: __iter__ geeft zichzelf terug en __next__ levert het volgende getal op of gooit StopIteration op. Een Countdown(0) levert helemaal niets op.

ScoreLog(scores) is een iterable, geen iterator. De __iter__ geeft elke keer een nieuwe LogWalker terug als daarom wordt gevraagd, zodat hetzelfde log twee keer kan worden doorlopen.

LogWalker(scores) is de iterator: die bewaart de positie, geeft zichzelf terug uit __iter__ en gooit StopIteration op zodra die voorbij het einde komt.

De beoordelaar doorloopt een ScoreLog twee keer en verwacht dat beide rondes alles zien, en doorloopt een Countdown twee keer en verwacht dat de tweede ronde leeg is. Dat verschil is het hele punt.