Iteratie als interface · oefening
Luie expressies en wanneer een lijst eenvoudiger is
Hoofdstuk 3 introduceerde de generatorexpressie als een comprehension zonder vierkante haakjes en beloofde later een reden. Dit is later.
[question.points for question in quiz] # builds a list
(question.points for question in quiz) # builds a generator object
Het woord voor wat de tweede doet is lui: de generator maken berekent geen enkele puntenwaarde. Die levert elke waarde pas op als erom wordt gevraagd en hoeft nooit het volledige resultaat tegelijk te bewaren.
Lui gedrag bekijken
Er wordt niets opgeleverd totdat iets erom vraagt. Vergelijk dat met een list comprehension, die alles uitvoert voordat die iets teruggeeft:
Dat is het hele verschil en al het andere volgt daaruit.
Wanneer luiheid ertoe doet
Vroeg stoppen. Als je alleen de eerste overeenkomst nodig hebt, doet een list comprehension toch al het werk:
Die controleerde 3, controleerde 9, vond de overeenkomst en stopte. De lijstversie zou alle vier hebben gecontroleerd.
Te veel om te bewaren. Een miljoen rijen in een lijst is een miljoen rijen in het geheugen. Lui doorgegeven, één voor één, is het één rij.
Stappen aaneenschakelen. Generators kunnen andere generators voeden en geen enkele stap bouwt een collectie:
Elke generator benoemt één stap zonder nog een collectie te bouwen. De oorspronkelijke scorelijst blijft bestaan en list(doubled) verzamelt het eindresultaat.
Stop zonder één extra item te nemen
Na twee resultaten stoppen moet ook betekenen dat je precies twee invoeritems opvraagt. Een for-lus vraagt de iterator om het volgende item voordat die het lusblok binnengaat, dus deze controle komt te laat:
for item in items:
if len(collected) == 2:
break
collected.append(item)
Tegen de tijd dat de voorwaarde twee verzamelde items ziet, heeft de lus al om een derde gevraagd. Controleer onmiddellijk nadat je het item hebt geaccepteerd waarmee de grens is bereikt:
def first_two(items):
taken = 0
for item in items:
yield item
taken = taken + 1
if taken == 2:
return
Bij een gevraagd aantal van nul of lager keer je terug voordat je de lus überhaupt ingaat. Dit is exact begrensd verbruik: de functie stopt zowel de resultaten als de verzoeken om invoer op de beloofde grens.
next(score for score in scores if score > 7) op [3, 9, 5, 8]. Hoeveel scores test de voorwaarde?
Wanneer een lijst eenvoudiger is, wat vaak zo is
Luiheid is niet gratis en de kosten zijn echt:
Je kunt die maar één keer doorlopen. De fout uit les 1, nu makkelijk per ongeluk te schrijven.
Er is geen lengte. len() op een generator gooit TypeError op. Indexeren ook.
Het is moeilijker te debuggen. Een lijst afdrukken toont de waarden. Een generator afdrukken toont dat het een generator is.
Het werk gebeurt ergens anders. Een exceptie die binnen een generator wordt opgegooid, verschijnt bij de for-lus die de generator gebruikt. Dat kan ver liggen van de code die verantwoordelijk lijkt.
Een korte, praktische regel:
| Je wilt | Gebruik |
|---|---|
Waarden doorgeven aan sum, any, all, min, max of next | een generatorexpressie |
| Vroeg stoppen op een grote of eindeloze bron | een generator |
| Iets teruggeven dat een aanroeper indexeert, meet of hergebruikt | een lijst |
| Het bekijken tijdens het debuggen | een lijst |
| Een handvol items | wat beter leest, meestal een lijst |
Voor tien vragen stelt de geheugenbesparing niets voor en is de leesbaarheid van een lijst meer waard dan de elegantie van luiheid.
Een generator teruggeven uit een functie
Als een functie een generator teruggeeft, zeg dat dan, omdat de aanroeper nu een eenmalig bruikbare waarde zonder lengte heeft:
def passing_records(records, mark):
"""Yield each record reaching the mark.
Returns a generator: walk it once, or wrap it in list() to keep it.
"""
Dat is de eerlijke afweging. De standaard voor een publieke methode die een collectie teruggeeft is een lijst. De uitzondering is wanneer de collectie echt groot is of de aanroeper echt vroeg stopt.
Opdracht
Implementeer preview(items, count). Geef een lijst terug met maximaal count items uit een mogelijk eindeloze bron.
De grens is exact: twee items nemen moet het derde item in een bestaande iterator onaangeroerd laten. Als count nul of negatief is, geef je een lege lijst terug zonder invoer op te vragen.
De gegeven functies first_failing, total_points, high_scorers en describe demonstreren de andere keuzes voor luie expressies en lijsten uit de les. Lees ze en voer ze uit, maar laat ze ongewijzigd.