0%

Hoofdstuk 11 · oefening

Iteratie als interface

Iterable en iterator

Je hebt honderden for-lussen geschreven. Dit is wat zo’n lus werkelijk doet.

for question in questions:
    print(question.prompt)

Python doorloopt questions niet via een index. Het vraagt questions om een iterator en vraagt die iterator vervolgens steeds om één item, totdat die meldt dat er geen items meer zijn. De lus is een gesprek met twee deelnemers. Weten wie wie is verklaart een hele reeks verwarrende gedragingen.

Twee verschillende dingen

Een iterable is alles waarover je kunt itereren. Een lijst, een string, een dictionary, een set, een bestand.

Een iterator is het ding dat het doorlopen doet. Die bewaart de positie. Die weet wat er hierna komt.

Meestal zijn ze niet hetzelfde object:

Try it

iter() vraagt een iterable om een iterator. Een lijst is niet de eigen iterator; die geeft elke keer een nieuwe als daarom wordt gevraagd. Een iterator geeft daarentegen zichzelf terug en behoudt dezelfde positie. Die is dus ook iterable:

Try it

Dat is het ontwerp en het heeft een gevolg dat je meteen kunt zien. Het diagram zegt bij de iterable ‘can make a fresh iterator’: kan een nieuwe iterator maken. De iterator ‘holds the position’, bewaart de positie. ‘One at a time’ betekent één voor één en ‘then the loop ends’ betekent dat de lus daarna eindigt.

Een lijst is een iterable die een nieuwe iterator maakt, terwijl de iterator steeds één item vooruitgaat.

Een iterator raakt uitgeput

Try it

De eerste list(walker) verzamelde alle drie. De tweede verzamelde niets. De iterator spoelde niet terug, omdat die geen weg terug heeft: die bewaart alleen een positie en die positie ligt nu voorbij het einde.

De lijst zelf is onaangeraakt:

Try it

Beide geven drie items, omdat elke list()-aanroep questions om een nieuwe iterator vroeg die aan het begin start.

Waarom werkt twee keer over dezelfde lijst itereren wel en twee keer over dezelfde iterator niet?

Waar dit problemen geeft

Dit is geen weetje. Het veroorzaakt een specifieke fout die lijkt op verdwijnende gegevens:

Try it

Dezelfde getallen, twee verschillende antwoorden. Met een lijst krijgt sum() een iterator en put die uit. Daarna vraagt list(scores) de lijst om een nieuwe: drie items. Met een iterator put sum() de enige iterator uit en vindt list(scores) niets meer.

De functie is niet zozeer fout als onvoldoende gespecificeerd: die doorloopt het argument twee keer en heeft dat nooit gezegd. Als je een functie schrijft die meer dan één keer over de invoer itereert, documenteer dan dat die een opnieuw doorloopbare collectie nodig heeft, of maak eerst een lijst van de invoer en wees duidelijk over de kosten.

Waarom die scheiding er is

Het zou eenvoudiger zijn als een lijst zichzelf doorliep. De scheiding levert twee dingen op.

Meerdere lussen tegelijk. Twee geneste lussen over dezelfde lijst krijgen elk een eigen positie, zodat het afronden van de binnenste lus de buitenste niet beëindigt.

Try it

Negen, niet drie. Met één gedeelde positie zou dat niet kunnen werken.

Dingen die te groot zijn om te bewaren. Een iterator hoeft alleen te weten hoe die het volgende item maakt. Niet alle items hoeven al te bestaan. Daardoor kan een programma een bestand dat groter is dan het geheugen regel voor regel lezen. Les 6 komt hierop terug.

De volgende les voert het gesprek met de hand, zodat het protocol geen beschrijving meer is maar iets wat je hebt gebruikt.

Opdracht

Schrijf drie kleine functies die een iterable en een iterator onderscheiden op basis van wat ze doen, niet op basis van hun typenaam.

  • walks_twice(items) geeft een tuple met twee lijsten terug: alles wat in een eerste ronde wordt verzameld en alles wat in een tweede ronde wordt verzameld. Een lijst geeft twee volle lijsten; een iterator geeft één volle en één lege.

  • is_own_iterator(items) meldt of het object zelf wordt teruggegeven als je het om een iterator vraagt. Dat is waar voor een iterator en onwaar voor een lijst.

  • safe_count(items) geeft het aantal items terug en moet voor beide werken. Niets wat twee keer over de invoer itereert kan dit doen.

Voer het programma uit om elk antwoord te zien voor een lijst en voor een iterator over dezelfde drie waarden.