Generatorer i Python

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 en StopIteration-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 generatorens yield et udtryk, der returnerer 10, og 10 tildeles til x.

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, og recovered 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 i finally.

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 derefter 3.

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.

YouTube Video