De voortgang van een bestaande iterator gebruiken.
Een echte iterator met __iter__ en __next__ maken.
Een herbruikbare iterable onafhankelijk laten doorlopen.
Een for-lus kan door een lijst, tekst of reeks getallen lopen. Toch zijn die objecten vanbinnen heel verschillend. Ze werken in dezelfde lus doordat Python een klein gemeenschappelijk iteratieprotocol gebruikt.
Een bron en haar voortgang onderscheiden
Een iterable is een object waarvan iter(source) een iterator kan maken. Een iterator bewaart de voortgang en levert met next(iterator) de volgende waarde. Wanneer er geen volgende waarde is, werpt hij StopIteration.
Een lijst is een iterable die bij iedere gewone iter-aanroep een nieuwe iterator oplevert. Twee van zulke iterators kunnen onafhankelijk door dezelfde lijst lopen. Een iterator is zelf ook iterable: iter(iterator) geeft diezelfde iterator terug.
Twee lijstiterators bewaren elk een eigen positie
values = ["A", "B", "C"]
left = iter(values)
right = iter(values)
print(next(left))
print(next(left))
print(next(right))
print(iter(left) is left)
Verwachte uitvoer
A
B
A
True
De eerste iterator in het voorbeeld kan verder zijn dan de tweede. De lijst heeft daarvoor niet zelf een algemene positie nodig. Iedere iterator bewaart zijn eigen plaats in de doorloop.
Wat de for-lus voor je regelt
Een for-lus vraagt eerst een iterator en daarna telkens de volgende waarde. De lus stopt wanneer de iterator StopIteration aangeeft. Die gewone uitputting verschijnt daarom niet als foutmelding buiten de lus.
Je kunt hetzelfde protocol handmatig gebruiken met iter en next. Dat is nuttig wanneer je één waarde wilt opvragen of juist de voortgang wilt bewaren tussen bewerkingen. Een handmatige next na uitputting werpt wel de echte StopIteration.
In advance vraag je straks een afgesproken aantal waarden van dezelfde ontvangen iterator op. remaining verzamelt later alleen wat van die doorloop over is. Geen van beide functies begint een al verbruikte doorloop opnieuw.
Een eigen iterator bouwen
Een eigen iteratorklasse heeft __iter__ en __next__. De eerste methode geeft de iterator zelf terug. De tweede geeft één volgende waarde terug en verplaatst de interne positie.
Het tweede voorbeeld gebruikt dat patroon voor een korte aftelling. De grenscontrole staat vóór het teruggeven van een volgende waarde. Na de laatste waarde blijven ook latere aanroepen uitgeput.
Een eigen iterator eindigt met StopIteration
class Countdown:
def __init__(self, start):
self.current = start
def __iter__(self):
return self
def __next__(self):
if self.current <= 0:
raise StopIteration
value = self.current
self.current -= 1
return value
counter = Countdown(2)
print(next(counter))
print(next(counter))
try:
next(counter)
except StopIteration:
print("klaar")
Verwachte uitvoer
2
1
klaar
return None is geen manier om uitputting te melden. None zou dan een gewone volgende waarde zijn. Gebruik raise StopIteration wanneer volgens het contract geen element meer bestaat.
Een herbruikbare bron maken
Niet elk object met __iter__ hoort zelf de iterator te zijn. De opdracht RepeatableSteps beschrijft juist een herbruikbare bron. Iedere aanroep van iter moet daar een nieuwe onafhankelijke iterator maken.
Zo kun je dezelfde bron twee keer doorlopen zonder dat een eerdere lus de volgende leeg heeft gemaakt. De bron bewaart de instelling voor de reeks; de iterator bewaart de positie van één specifieke doorloop. Het onderscheid tussen die twee toestanden voorkomt onbedoeld gedeelde voortgang.
Veelgemaakte fout: de iterator telkens opnieuw maken
Met next(iter(values)) begin je bij een lijst telkens aan een nieuwe doorloop. Daardoor zie je opnieuw het eerste element. Bewaar eerst de iterator en gebruik daarna herhaaldelijk next op datzelfde object.
Andersom kan een bestaande iterator niet vanzelf opnieuw beginnen. Als je hem met list(iterator) volledig verbruikt, is een volgende omzetting naar een lijst leeg. Voor een verse doorloop heb je dan een nieuwe iterator uit een herbruikbare bron nodig.
Lege invoer is ook een geldige reeks
Een bron kan direct uitgeput zijn. De eerste handmatige next moet dan StopIteration geven, terwijl een for-lus gewoon nul iteraties uitvoert. Verzin geen extra nul of andere eindmarker als de opdracht een lege reeks beschrijft.
De controles onderzoeken daarom zowel ontvangen waarden als onafhankelijke voortgang en echte uitputting. Een lijst teruggeven kan wel dezelfde waarden bevatten, maar is nog geen iterator met de gevraagde next-werking.
AAN DE SLAG 0/4
1
Waarden van dezelfde iterator opvragen
Maak advance(iterator, count). Geef exact count volgende waarden als lijst terug, in volgorde. Nul verbruikt niets; een negatieve count geeft ValueError("aantal mag niet negatief zijn") vóór enige bronlezing. Is de bron te kort, laat dan de echte StopIteration doorlopen. count is een int.
2
Een echte iterator met eigen positie maken
De constructor en __iter__ van CountUp zijn gegeven. Vul __next__ aan: lever achtereenvolgens ints van 0 tot stop exclusief, verhoog current en werp daarna StopIteration zonder bericht. Bij stop <= 0 is de iterator direct uitgeput; ook herhaald next na uitputting blijft StopIteration geven.
3
Voor elke doorloop een nieuwe iterator leveren
De constructor van RepeatableSteps is gegeven. Laat __iter__ telkens een nieuwe CountUp(self.stop) teruggeven. De bron zelf is niet de iterator, de instelling stop blijft gelijk en twee doorlopen hebben onafhankelijke posities.
4
Alleen het resterende deel verzamelen
Maak remaining(iterator): geef een nieuwe lijst met alle nog beschikbare waarden uit diezelfde iterator, in hun volgorde. Verbruik die resterende waarden daadwerkelijk; behoud typen, duplicaten en objectidentiteiten. Een lege of uitgeputte iterator geeft een lege lijst.
Onthoud dit
Een iterable levert een iterator; de iterator bewaart de voortgang.
next geeft één waarde of werpt StopIteration.
Een herbruikbare bron en één doorloop hebben ieder hun eigen toestand.
Hierna schrijf je dezelfde stapsgewijze levering compacter met een generatorfunctie.
Je hoeft het niet in één keer goed te doen. Na het uitvoeren zie je hier welke stappen al kloppen.
1Waarden van dezelfde iterator opvragen2Een echte iterator met eigen positie maken3Voor elke doorloop een nieuwe iterator leveren4Alleen het resterende deel verzamelen