Generatorer i Python
Denne artikel forklarer generatorer i Python.
YouTube Video
Generatorer i Python
Oversigt
Python generatorer er en type iterator og en kraftfuld funktion til at udføre gentagende behandling effektivt. De gør det muligt at skrive hukommelseseffektiv kode, når du arbejder med store datamængder.
Hvad er en generator?
En generator i Python er en særlig funktion, der producerer én værdi ad gangen, defineret ved hjælp af nøgleordet yield
. Dens karakteristiske træk er, at den pauser udførelsen, mens den bevarer sin tilstand og kan genoptages senere.
Grundlæggende om yield
yield
er et nøgleord, der returnerer en værdi og pauser funktionens udførelse på samme tid.
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
- Når funktionen kaldes, returnerer den et generatorobjekt, der leverer værdier én efter én.
- Hvis du kalder
next()
, når der ikke er flere værdier, vil enStopIteration
-fejl opstå.
next()
og 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")
- Ved at håndtere
StopIteration
-fejlen eksplicit på denne måde, kan du registrere, hvornår en generator er færdig.
send(value)
Når du kalder send(value)
, genoptages generatoren, og value
sendes til positionen af yield
-udtrykket. Den sendte værdi kan modtages på generatorsiden som returneringsværdien af yield
-udtrykket. Ved det første kald kan du ikke sende andet end None
med send(value)
, så du skal bruge next()
eller send(None)
.
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
- Med
send(10)
bliver generatorensyield
et udtryk, der returnerer 10, og 10 tildeles tilx
.
throw()
Når du kalder throw
, genoptages generatoren, og der kastes en undtagelse på positionen af den pauseholdte yield
. Du kan håndtere undtagelsen inde i generatoren for at fortsætte behandlingen. Hvis undtagelsen ikke opfanges, spredes den udad, og generatoren afsluttes.
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"
- I denne kode kaldes
throw
for at indsende en undtagelse i generatoren. På generatorsiden håndteres undtagelsen, ogrecovered
returneres.
close()
Når du kalder close()
, afsluttes generatoren. Inde i generatoren kan du udføre oprydning med finally
. At kalde next()
eller send()
efter at have kaldt close()
udløser en StopIteration
-fejl.
1def gen():
2 try:
3 yield 1
4 finally:
5 print("Cleaning up...")
6
7g = gen()
8print(next(g)) # -> 1
9g.close() # -> Cleaning up...
- Denne kode viser, at kald af
close()
afslutter generatoren og aktiverer oprydningsprocessen ifinally
.
yield from
yield from
er syntaks, der bruges til at delegere til en subgenerator. Det er en enkel måde at kalde en anden generator inde i en generator og videregive alle dens værdier til det ydre 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]
- Denne kode delegérer alle værdier fra subgeneratoren til den ydre generator ved at bruge
yield from
, og returnerer derefter3
.
Forhold til iteratorer
Generatorer implementerer internt __iter__()
og __next__()
, hvilket gør dem til en type iterator. Derfor er de fuldt kompatible med itererbare operationer såsom for
-løkker.
Integration med for
-løkker
I Python bruger en for
-løkke internt next()
til automatisk at hente værdier.
1def simple_generator():
2 yield 1
3 yield 2
4 yield 3
5
6for value in simple_generator():
7 print(value)
Med denne metode håndteres StopIteration
også automatisk.
Oprettelse af uendelige generatorer
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
Det er muligt at oprette uendelige løkker, men man skal være forsigtig, når man bruger dem.
Generatorudtryk
Generatorudtryk, skrevet med parenteser, giver dig mulighed for at definere generatorer med en syntaks, der ligner listeforståelser (list comprehensions).
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)
I modsætning til listeforståelser indlæser de ikke alle elementer i hukommelsen på én gang, hvilket gør dem mere hukommelseseffektive.
Fejlhåndtering i generatorer
Undtagelser kan opstå inde i en generator. I sådanne tilfælde bruger du try-except
ligesom i almindelig Python-kode.
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
I dette eksempel udføres korrekt fejlhåndtering i tilfælde af division med nul.
Stack trace af en generator
Hvis en undtagelse opstår inde i generatoren, vil den blive kastet, når generatoren genoptages.
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)
- Denne generator returnerer først
1
. Fejlen, der opstår ved genoptagelse, fanges og vises som en fejlmeddelelse.
Eksempler på brug af generatorer
Læse en fil linje for linje (velegnet til store filer)
1def read_large_file(filepath):
2 with open(filepath, 'r') as f:
3 for line in f:
4 yield line.strip()
- Denne funktion læser en tekstfil linje for linje ved hjælp af en iterator, fjerner mellemrum fra hver linje og returnerer det som en generator, så store filer kan behandles med lavt hukommelsesforbrug.
Generator for Fibonacci-sekvensen
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)
- Denne kode bruger en generator til sekventielt at generere Fibonaccital, der er mindre end den øvre grænse, og udskriver dem ved hjælp af en
for
-løkke.
Anvendelsestilfælde
Generatorer kan også bruges i følgende scenarier.
- Sekventiel behandling af store CSV- eller logfiler
- API-paginering
- Behandling af streamingdata (f.eks. Kafka, IoT-enheder)
Sammendrag
Begreb | Hovedpunkt |
---|---|
yield |
Pauser og returnerer en værdi |
Generatorfunktion | En funktion, der indeholder yield og returnerer en iterator, når den kaldes |
Fordele | Hukommelseseffektiv og ideel til behandling af store datasæt |
Generatorudtryk | Muliggør en kortfattet syntaks som (x for x in iterable) |
Ved at bruge generatorer kan du effektivt behandle store datasæt, samtidig med at du sparer hukommelse og holder din kode kortfattet.
Du kan følge med i ovenstående artikel ved hjælp af Visual Studio Code på vores YouTube-kanal. Husk også at tjekke YouTube-kanalen.