Generatoren in Python
Dit artikel legt generatoren in Python uit.
YouTube Video
Generatoren in Python
Overzicht
Python generatoren zijn een soort iterator en een krachtig hulpmiddel om herhaalde bewerkingen efficiënt uit te voeren. Ze maken het mogelijk geheugenefficiënte code te schrijven wanneer je veel gegevens verwerkt.
Wat is een generator?
Een generator in Python is een speciale functie die één waarde tegelijk oplevert, gedefinieerd met het yield
-trefwoord. Het kenmerk is dat de uitvoering wordt gepauzeerd terwijl de status behouden blijft en later kan worden hervat.
De basis van yield
yield
is een trefwoord dat een waarde oplevert en tegelijkertijd de uitvoering van de functie pauzeert.
1def simple_generator():
2 yield 1
3 yield 2
4 yield 3
5
6gen = simple_generator()
7
8print(next(gen)) # 1
9print(next(gen)) # 2
10print(next(gen)) # 3
- Wanneer aangeroepen, retourneert deze functie een generatorobject dat waarden één voor één oplevert.
- Als je
next()
aanroept wanneer er geen volgende waarde is, treedt er eenStopIteration
-fout op.
next()
en StopIteration
1def simple_generator():
2 yield 1
3 yield 2
4 yield 3
5
6gen = simple_generator()
7
8try:
9 while True:
10 value = next(gen)
11 print(value)
12except StopIteration:
13 print("Finished")
- Door de
StopIteration
-fout expliciet op deze manier af te handelen, kun je detecteren wanneer een generator klaar is.
send(value)
Het aanroepen van send(value)
hervat de generator en stuurt value
naar de positie van de yield
-expressie. De verzonden waarde kan aan de kant van de generator worden ontvangen als de terugkeerwaarde van de yield
-expressie. Bij de eerste aanroep kun je niets anders dan None
verzenden met send(value)
, dus je moet next()
of send(None)
gebruiken.
1def gen():
2 x = yield 1
3 print(f"x = {x}")
4 y = yield 2
5 print(f"y = {y}")
6
7g = gen()
8print(next(g)) # -> 1 (value from yield 1)
9print(g.send(10)) # -> x = 10, 2 (value from yield 2)
10print(g.send(20)) # -> y = 20, StopIteration occurs
- Met
send(10)
wordt deyield
van de generator een expressie die 10 retourneert en wordt 10 toegewezen aanx
.
throw()
Het aanroepen van throw
hervat de generator en werpt een uitzondering op de positie van de gepauzeerde yield
. Je kunt de uitzondering binnen de generator afhandelen om de verwerking voort te zetten. Als de uitzondering niet wordt opgevangen, wordt deze naar buiten doorgegeven en stopt de generator.
1def gen():
2 try:
3 yield 1
4 except ValueError as e:
5 print(f"Caught: {e}")
6 yield "recovered"
7
8g = gen()
9print(next(g)) # -> 1
10print(g.throw(ValueError("boom"))) # -> Caught: boom, "recovered"
- In deze code wordt
throw
aangeroepen om een uitzondering in de generator te injecteren. Aan de zijde van de generator wordt de uitzondering afgehandeld en wordtrecovered
geretourneerd.
close()
Het aanroepen van close()
beëindigt de generator. Binnen de generator kun je opruimacties uitvoeren met behulp van finally
. Het aanroepen van next()
of send()
na het aanroepen van close()
veroorzaakt een StopIteration
-fout.
1def gen():
2 try:
3 yield 1
4 finally:
5 print("Cleaning up...")
6
7g = gen()
8print(next(g)) # -> 1
9g.close() # -> Cleaning up...
- Deze code laat zien dat het aanroepen van
close()
de generator beëindigt en het opruimproces infinally
activeert.
yield from
yield from
is syntaxis die wordt gebruikt voor delegatie naar een subgenerator. Het is een eenvoudige manier om een andere generator binnen een generator aan te roepen en al zijn waarden door te geven aan de buitenste scope.
1def sub_gen():
2 yield 1
3 yield 2
4
5def main_gen():
6 yield from sub_gen()
7 yield 3
8
9print(list(main_gen())) # -> [1, 2, 3]
- Deze code delegeert alle waarden van de subgenerator naar de buitengenerator met
yield from
en geeft daarna3
terug.
Relatie met iterators
Generatoren implementeren intern __iter__()
en __next__()
, waardoor ze een soort iterator zijn. Daarom zijn ze volledig compatibel met iterabele bewerkingen, zoals for
-lussen.
Integratie met for
-lussen
In Python gebruikt een for
-lus intern next()
om automatisch waarden op te halen.
1def simple_generator():
2 yield 1
3 yield 2
4 yield 3
5
6for value in simple_generator():
7 print(value)
Met deze methode wordt de afhandeling van StopIteration
ook automatisch gedaan.
Oneindige generatoren maken
1def count_up(start=0):
2 while True:
3 yield start
4 start += 1
5
6counter = count_up()
7print(next(counter)) # 0
8print(next(counter)) # 1
Het is mogelijk om oneindige lussen te maken, maar wees voorzichtig bij het gebruik hiervan.
Generatorexpressies
Generatorexpressies, geschreven met haakjes, maken het mogelijk om generatoren te definiëren met een syntaxis vergelijkbaar met lijstcomprehensies.
1# List comprehension (generates the entire list at once)
2squares_list = [x**2 for x in range(5)]
3print(squares_list)
4
5# Generator expression
6squares_gen = (x**2 for x in range(5))
7for square in squares_gen:
8 print(square)
In tegenstelling tot lijstcomprehensies laden ze niet alle elementen ineens in het geheugen, waardoor ze efficiënter zijn in geheugenverbruik.
Foutafhandeling in generatoren
Binnen een generator kunnen uitzonderingen optreden. In zulke gevallen gebruik je try-except
, net als bij gewone Python-code.
1def safe_divide_generator(numbers, divisor):
2 """Yields results of dividing numbers by a given divisor safely."""
3 for number in numbers:
4 try:
5 yield number / divisor # Attempt to divide and yield result.
6 except ZeroDivisionError:
7 yield float('inf') # Return infinity if division by zero occurs.
8
9# Example usage
10numbers = [10, 20, 30]
11gen = safe_divide_generator(numbers, 0) # Create generator with divisor as 0.
12for value in gen:
13 print(value) # Output: inf, inf, inf
In dit voorbeeld wordt correcte foutafhandeling uitgevoerd in het geval van deling door nul.
Stacktrace van een generator
Als er een uitzondering optreedt binnen de generator, wordt deze opgeworpen wanneer de generator wordt hervat.
1def error_generator():
2 """A generator that yields values and raises an error."""
3 yield 1
4 raise ValueError("An error occurred") # Raise a ValueError intentionally.
5 yield 2
6
7gen = error_generator()
8print(next(gen)) # Output: 1 (first value yielded)
9try:
10 print(next(gen)) # Attempt to get the next value, which raises an error
11except ValueError as e:
12 print(e) # Output: An error occurred (exception message is printed)
- Deze generator geeft eerst
1
terug. De fout die bij het hervatten wordt opgeworpen, wordt opgevangen en als een foutmelding weergegeven.
Voorbeelden van het gebruik van generatoren
Een bestand regel voor regel lezen (geschikt voor grote bestanden)
1def read_large_file(filepath):
2 with open(filepath, 'r') as f:
3 for line in f:
4 yield line.strip()
- Deze functie leest een tekstbestand regel voor regel met een iterator, verwijdert witruimte van elke regel, en geeft het terug als een generator, zodat grote bestanden met een laag geheugengebruik verwerkt kunnen worden.
Generator voor de Fibonacci-reeks
1def fibonacci(limit):
2 a, b = 0, 1
3 while a < limit:
4 yield a
5 a, b = b, a + b
6
7for n in fibonacci(100):
8 print(n)
- Deze code gebruikt een generator om opeenvolgende Fibonacci-getallen kleiner dan de bovengrens te genereren en voert ze uit met een
for
-lus.
Toepassingen
Generators kunnen ook in de volgende scenario's worden gebruikt.
- Sequentiële verwerking van grote CSV- of logbestanden
- API-paginering
- Verwerken van streaminggegevens (zoals Kafka, IoT-apparaten)
Samenvatting
Begrip | Belangrijk punt |
---|---|
yield |
Pauzeert en geeft een waarde terug |
Generatorfunctie | Een functie die yield bevat en een iterator retourneert wanneer deze wordt aangeroepen |
Voordelen | Geheugenefficiënt en ideaal voor het verwerken van grote datasets |
Generatorexpressie | Staat een beknopte syntaxis toe zoals (x for x in iterable) |
Door generatoren te gebruiken kun je grote datasets efficiënt verwerken, terwijl je geheugen bespaart en je code beknopt houdt.
Je kunt het bovenstaande artikel volgen met Visual Studio Code op ons YouTube-kanaal. Bekijk ook het YouTube-kanaal.