Osa 12

Generatorer

Vi har redan stött på situationer där vi har att göra med en serie föremål och vi behöver nästa föremål i serien, men vi vill inte nödvändigtvis formulera hela serien fram till den punkten varje gång ett nytt föremål krävs. Vissa rekursiva serier, till exempel Fibonacci-numret, är ett bra exempel på en sådan situation. Om varje funktionsanrop rekursivt genererar hela serien fram till önskad punkt, slutar det med att vi genererar början av serien många gånger om.

Python-generatorer är ett sätt att bara producera nästa föremål i en serie när det behövs, vilket i princip innebär att genereringsprocessen för serien bara körs en gång (för en viss exekvering av ett program). De fungerar i stort sett som vanliga funktioner, eftersom de kan anropas och returnerar värden, men det värde som en generatorfunktion returnerar skiljer sig från en vanlig funktion. En normal funktion ska returnera samma värde varje gång, givet samma argument. En generatorfunktion, å andra sidan, ska komma ihåg sitt nuvarande tillstånd och returnera nästa föremål i serien, som kan skilja sig från föregående föremål.

Precis som det finns många sätt att lösa de flesta programmeringsproblem finns det många sätt att uppnå en funktionalitet som liknar generatorer, men generatorer kan bidra till att göra programmet lättare att förstå och kan i vissa situationer spara minne eller andra beräkningsresurser.

Nyckelordet yield

En generatorfunktion måste innehålla nyckelordet yield, som markerar det värde som funktionen returnerar. Låt oss titta på en funktion som genererar heltal, med början från noll och slut vid ett förutbestämt maxvärde:


def raknare(maximum: int):
    tal = 0
    while tal <= maximum:
        yield tal
        tal += 1

Nu kan raknare-funktionen skickas som argument till funktionen next()

if __name__ == "__main__":
    talen = raknare(10)
    print("Första värde:")
    print(next(talen))
    print("Andra värde:")
    print(next(talen))
Exempelutskrift

Första värde: 0 Andra värde: 1

Som du kan se i exemplet ovan liknar nyckelordet yield nyckelordet return: båda används för att definiera ett returvärde. Skillnaden är att yield inte "stänger" funktionen på samma sätt som return. En generatorfunktion med nyckelordet yield håller reda på sitt tillstånd och nästa gång den anropas kommer den att fortsätta från samma tillstånd.

Den här generatorn kräver också ett maxvärde, i exemplet ovan var det 10. När generatorn får slut på värden kommer den att ge upphov till ett StopIteration-undantag:

if __name__ == "__main__":
    talen = raknare(1)
    print(next(talen))
    print(next(talen))
    print(next(talen))
Exempelutskrift
0 1 Traceback (most recent call last): File "generatorexempel.py", line 11, in print(next(talen)) StopIteration

Undantaget kan bli fångat med ett try- except block:

if __name__ == "__main__":
    talen = raknare(1)
    try:
        print(next(talen))
        print(next(talen))
        print(next(talen))
    except StopIteration:
        print("Talen tog slut")
Exempelutskrift

0 1 Talen tog slut

Att gå igenom alla objekt i en generator görs enkelt med en for-loop:

if __name__ == "__main__":
    talen = raknare(5)
    for tal in talen:
        print(tal)
Exempelutskrift

0 1 2 3 4 5

Generatorer behöver inte ha ett definierat maxvärde eller en slutpunkt. De kan generera värden i det oändliga (naturligtvis inom andra beräkningsmässiga och fysiska begränsningar).

Tänk dock på att det bara fungerar att genomkorsa en generator med en for-loop om generatorn avslutas vid någon punkt. Om generatorn är uppbyggd på en oändlig loop kommer en enkel for-loop att orsaka en oändlig exekvering, precis som en while-loop utan slut- eller brytvillkor.

Loading
Loading

Generator comprehensions

Du behöver inte nödvändigtvis en funktionsdefinition för att skapa en generator. Vi kan använda en struktur som liknar en list comprehension istället. Den här gången använder vi runda parenteser för att beteckna en generator i stället för en lista eller en ordlista:

# Generatorn returnerar kvadraten av heltal
kvadrat = (x ** 2 for x in range(1, 64))

print(kvadrat)

for i in range(5):
    print(next(kvadrat))
Exempelutskrift

<generator object <genexpr> at 0x000002B4224EBFC0> 1 4 9 16 25

I följande exempel skriver vi ut delsträngar av det engelska alfabetet, var och en tre tecken lång. Detta skriver ut de första 10 objekten i generatorn:

delstrangar = ("abcdefghijklmnopqrstuvwxyz"[i : i + 3] for i in range(24))

# skriver ut 10 första delsträngarna
for i in range(10):
    print(next(delstrangar))
Exempelutskrift

abc bcd cde def efg fgh ghi hij ijk jkl

Loading
Du har nått slutet av den här delen! Fortsätt till nästa del:

Se dina poäng genom att klicka på cirkeln nere till höger av sidan.