Poznajemy nieco bliżej generatory i słówko kluczowe yield. Dotychczas generatory stanowiły dla nas nieco bardziej abstrakcyjną wersję pętli for i while. W tym epizodzie zobaczymy, że jednak są czymś nieco większym niż pętla i zrozumiemy, jak działa słówko kluczowe yield.

Generator kontra pętla

Wydawać by się mogło, że generatory to bardziej zawiły sposób pisania pętli w Pythonie, który tworzy nam więcej kodu, niż jest to potrzebne zaś jego re-używalność jest dyskusyjna.

Zobaczmy na ten przykład pętli wypisującej liczby od 1 do 3:

def counter_generator(num):
    for num in range(1, num+1):
        yield num

for n in counter_generator(3):
    print(n)
# 1
# 2
# 3

Czy da się to zapisać bez generatora? Oczywiście:

for n in range(1, 4):
    print(n)
# 1
# 2
# 3

Czy potrzebowaliśmy osobnej funkcji do tego, skoro i tak pod spodem wołamy range? Nie.

Czym zatem generator różni się od pętli?

Dobre pytanie.

Słówko kluczowe yield – pauza

Patrząc na poniższy przykład, zastanówmy się, gdzie leży klucz do tego ile razy generator się może wywołać:

def counter_generator(num):
    for num in range(1, num+1):
        yield num

Może nam się wydawać, że o ilości zwracanych nam elementów decyduje pętla for. Jeżeli num będzie wynosić 3 – dostaniemy 3 elementy w pętli.

Błąd.

O ilości zwracanych elementów decyduje ilość słówek yield, jakie się w generatorze znajdują:

def counter_generator(num):
    for num in range(1, num+1):
        yield num
        yield num


for n in counter_generator(3):
    print(n)
    
# 1
# 1
# 2
# 2
# 3
# 3

Słówko 'yield’ oznacza pauzę w iteracji. Oznacza, że zwracamy jeden element. Jeżeli nie ma już żadnego yield – mamy koniec generatora. Jeżeli jest jeszcze jakieś yield – generator kontynuuje swoją pracę, od momentu, w którym skończył.

Ten generator wywoła się tylko dwa razy:

def weird_generator(num):
    yield num
    num +=1
    yield num

for n in weird_generator(3):
    print(n)

# 3
# 4

Po pierwszej iteracji, która zwraca num, kontynuuje tam gdzie skończył, podnosi num o 1 i zwraca. Nie ma już więcej yieldów, nie jesteśmy w pętli, która mogłaby kolejne yieldy wygenerować – koniec iteracji.

Generatory to taki temat, który jest dziwny i trudno wyobrazić sobie jego praktyczne zastosowanie – do czasu, aż napotkamy taki przypadek, w którym akurat nam się przydadzą.

Mini-zadanie – liczba, kwadrat, sześcian

Zadanie jest proste – napisać funkcję, która przyjmuje liczbę n i zwraca liczby od 1 do n włącznie, zwracając liczbę, jej kwadrat i sześcian.

Potrzebna zatem pętla i trzy słówka kluczowe yield:

def square_cube_gen(num):
    for num in range(1,num+1):
        yield num
        yield num ** 2
        yield num ** 3

for n in square_cube_gen(3):
    print(n)

# 1
# 1
# 1
# 2
# 4
# 8
# 3
# 9
# 27

Jeżeli rozumiemy powyższy kod, to jesteśmy na dobrej drodze. Rozumiemy, jak działają generatory, jaki mają potencjał, oraz to, że są czymś więcej niż funkcją zwracającą pętlę.

Teraz tylko wymyślić dla nich jakieś realne zastosowanie w ramach zadania domowego!