Een begrensd voorstuk
Vul first_n(items, n) in voor een niet-negatieve n: geef een lijst met hoogstens de eerste n waarden. Lees geen extra waarde; n=0 mag de bron niet aanspreken.
Een logbestand kan honderdduizend regels bevatten, terwijl je op dit moment alleen de eerste vijf meldingen nodig hebt. Als je alles eerst naar een lijst omzet, betaal je alvast voor werk dat je niet gebruikt. Met luie verwerking laat je de vraag van de afnemer bepalen hoeveel de bron levert.
Een iterator onthoudt waar hij gebleven is en levert bij next hoogstens één volgende waarde. Je hebt generators al gebruikt bij bestanden. Lui betekent hier dat de volgende berekening of bronlezing pas gebeurt wanneer de afnemer daarom vraagt.
Dat is geen belofte dat iedere bewerking sneller wordt. Een generator heeft ook overhead, en sommige algoritmen hebben alle gegevens tegelijk nodig. Het voordeel is dat je over de hoeveelheid vooraf verricht werk kunt redeneren.
islice uit itertools begrenst hoeveel waarden je uit een iterator opvraagt. list(islice(source, 3)) maakt wel een lijst, maar alleen met de eerste drie waarden. De rest van de bron wordt niet alvast opgehaald.
from itertools import islice
def measurements():
for value in [12, 15, 19]:
print(f"lees {value}")
yield value
source = measurements()
print("gemaakt")
print(list(islice(source, 2)))gemaakt lees 12 lees 15 [12, 15]
De printregels van de bron laten zien wanneer er werkelijk een waarde wordt gevraagd. Het aanmaken van de generator print nog niets. Na twee verzoeken zijn ook maar twee bronwaarden voorbijgekomen.
Een generatorfunctie met yield kan eerst een waarde beoordelen en daarna een omgevormde waarde leveren. Niet iedere gelezen bronwaarde hoeft dus een uitvoerwaarde te worden. Voor de volgende positieve waarde moet je misschien meerdere ongeschikte waarden passeren.
Vraag je maar één resultaat, dan hoort de generator na dat resultaat te pauzeren. Een lijstcomprehension leest de hele bron voordat de aanroeper iets ontvangt. Beide kunnen dezelfde uiteindelijke lijst maken, maar hebben een andere werkwijze onderweg.
batched verzamelt maximaal n bronwaarden in een tuple. De laatste groep mag korter zijn. De functie leest geen volgende groep vooruit; nul of een negatieve groepsgrootte is ongeldig en geeft ValueError.
accumulate levert telkens het totaal tot en met de zojuist gelezen waarde. Met [3, 4, -2] krijg je zo 3, 7, 5. Zonder een expliciete beginwaarde verschijnt bij lege invoer geen totaal.
Een batch bewaart de eigen groep en accumulate de lopende toestand. Lui betekent dus niet dat er helemaal geen geheugen wordt gebruikt. Het betekent dat deze hulpmiddelen niet automatisch een lijst van alle toekomstige resultaten opbouwen.
Een generator om een al gemaakte lijst zetten maakt de eerdere bronlezing niet ongedaan. Bij iter(list(source)) is de volledige bron immers al verbruikt. Bij een grote of onbegrensde bron is dat precies wat je wilde vermijden.
events = []
def source():
for value in [4, 5, 6]:
events.append(value)
yield value
eager = iter(list(source()))
print(events)
events.clear()
lazy = (value * 2 for value in source())
print(events)
print(next(lazy))
print(events)[4, 5, 6] [] 8 [4]
De gebeurtenissen maken het verschil zichtbaar zonder kwetsbare milliseconden te vergelijken. De ene vorm vraagt alles vóór het eerste resultaat; de andere leest alleen voor dat resultaat. De controles in de oefening gebruiken dezelfde soort echte bronwaarneming.
De functies ontvangen een iterable die maar eenmaal doorlopen hoeft te kunnen worden. Gebruik daarom geen lengtecontrole, indexering of tweede volledige ronde over de bron. Positieve kwadraten slaan nul en negatieve getallen over; de overige functies behouden alle geleverde waarden.
Vul first_n(items, n) in voor een niet-negatieve n: geef een lijst met hoogstens de eerste n waarden. Lees geen extra waarde; n=0 mag de bron niet aanspreken.
Vul positive_squares(items) in als generator: sla getallen <=0 over en yield de kwadraten van de overige getallen. Lees niets bij aanmaken en pauzeer direct na iedere geleverde waarde.
Vul batches(items, size) in: geef een iterator die tuples van hoogstens size waarden levert, met een eventueel kortere laatste groep. Geen voorafgaande bronlezing; size<=0 geeft ValueError.
Vul running_totals(items) in: geef een iterator met het lopende opteltotaal. Lees pas wanneer een resultaat gevraagd wordt, lees geen waarde vooruit en lever niets voor een lege bron.
islice begrenst consumptie; batched groepeert; accumulate houdt een tussenstand bij.Hierna geef je functies extra gedrag met een wrapper, zonder hun oorspronkelijke contract te verliezen.
Voer je code uit en ontdek
wat je hebt gemaakt.
Je hoeft het niet in één keer goed te doen. Na het uitvoeren zie je hier welke stappen al kloppen.