Generatorer i Python

Generatorer i Python

Denna artikel förklarar generatorer i Python.

YouTube Video

Generatorer i Python

Översikt

Python-generatorer är en typ av iterator och en kraftfull funktion för att utföra upprepande bearbetning effektivt. De gör det möjligt att skriva minnes­effektiv kod när du arbetar med stora datamängder.

Vad är en generator?

En generator i Python är en speciell funktion som producerar ett värde åt gången, definierad med nyckelordet yield. Dess kännetecken är att den pausar exekveringen men behåller sitt tillstånd och kan återupptas senare.

Grunderna i yield

yield är ett nyckelord som returnerar ett värde och pausar funktions­exekveringen samtidigt.

 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 den anropas returnerar funktionen ett generatorobjekt som avger värden ett i taget.
  • Om du anropar next() när det inte finns något nästa värde, kommer ett StopIteration-fel att uppstå.

next() och 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")
  • Genom att hantera StopIteration-felet explicit på detta sätt kan du upptäcka när en generator har avslutats.

send(value)

Att anropa send(value) återupptar generatorn och skickar value till positionen för yield-uttrycket. Det skickade värdet kan tas emot på generatorsidan som returvärdet från yield-uttrycket. Vid det första anropet kan du inte skicka något annat än None med send(value), så du måste använda 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) blir generatorns yield ett uttryck som returnerar 10, och 10 tilldelas x.

throw()

Att anropa throw återupptar generatorn och kastar ett undantag vid positionen för den pausade yield. Du kan hantera undantaget inne i generatorn för att fortsätta bearbetningen. Om undantaget inte fångas, propagerar det utåt och generatorn avslutas.

 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 denna kod anropas throw för att injicera ett undantag i generatorn. På generatorsidan hanteras undantaget och recovered returneras.

close()

Att anropa close() avslutar generatorn. Inne i generatorn kan du utföra städning med hjälp av finally. Att anropa next() eller send() efter att ha anropat close() ger ett StopIteration-fel.

1def gen():
2    try:
3        yield 1
4    finally:
5        print("Cleaning up...")
6
7g = gen()
8print(next(g))  # -> 1
9g.close()       # -> Cleaning up...
  • Denna kod visar att anrop av close() avslutar generatorn och utlöser städrutinen i finally.

yield from

yield from är en syntax som används för delegering till en subgenerator. Det är ett enkelt sätt att anropa en annan generator inuti en generator och skicka vidare alla dess värden till det yttre omfånget.

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]
  • Den här koden delegerar alla värden från subgeneratorn till den yttre generatorn med hjälp av yield from, och returnerar sedan 3.

Relation till iteratorer

Generatorer implementerar internt __iter__() och __next__(), vilket gör dem till en typ av iterator. Därför är de helt kompatibla med itererbara operationer såsom for-loopar.

Integration med for-loopar

I Python använder en for-loop internt next() för att automatiskt hämta värden.

1def simple_generator():
2    yield 1
3    yield 2
4    yield 3
5
6for value in simple_generator():
7    print(value)

Med denna metod hanteras även StopIteration automatiskt.

Skapa oändliga 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 är möjligt att skapa oändliga loopar, men man måste vara försiktig vid användning.

Generatorexpressioner

Generatorexpressioner, skrivna med parenteser, gör det möjligt att definiera generatorer med en syntax liknande listförståelser.

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)

Till skillnad från listförståelser laddar de inte in alla element i minnet på en gång, vilket gör dem mer minnes­effektiva.

Felhantering i generatorer

Undantag kan uppstå inuti en generator. I sådana fall använder du try-except precis som i vanlig Python-kod.

 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 det här exemplet utförs korrekt felhantering vid division med noll.

Stackspårning för en generator

Om ett undantag uppstår inuti generatorn kommer det att kastas när generatorn återupptas.

 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)
  • Denna generator returnerar först 1. Felet som kastas vid återupptagande fångas och visas som ett felmeddelande.

Exempel på användning av generatorer

Läsa en fil rad för rad (lämpligt för stora filer)

1def read_large_file(filepath):
2    with open(filepath, 'r') as f:
3        for line in f:
4            yield line.strip()
  • Denna funktion läser en textfil rad för rad med en iterator, tar bort blanksteg från varje rad och returnerar den som en generator, vilket gör det möjligt att bearbeta stora filer med låg minnesanvändning.

Generator för Fibonaccitalföljden

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)
  • Denna kod använder en generator för att sekventiellt generera Fibonaccital mindre än den övre gränsen och skriver ut dem med en for-loop.

Användningsområden

Generatorer kan också användas i följande scenarier.

  • Sekventiell bearbetning av stora CSV- eller loggfiler
  • API-paginering
  • Bearbeta strömmande data (t.ex. Kafka, IoT-enheter)

Sammanfattning

Koncept Nyckelpunkt
yield Pausar och returnerar ett värde
Generatorfunktion En funktion som innehåller yield och returnerar en iterator när den anropas
Fördelar Minneseffektivt och idealiskt för bearbetning av stora datamängder
Generatorexpression Tillåter en kortfattad syntax såsom (x for x in iterable)

Genom att använda generatorer kan du effektivt bearbeta stora datamängder samtidigt som du sparar minne och håller din kod kortfattad.

Du kan följa med i artikeln ovan med hjälp av Visual Studio Code på vår YouTube-kanal. Vänligen kolla även in YouTube-kanalen.

YouTube Video