Blog JSystems - uwalniamy wiedzę!

Szukaj
Animacja generatora - leniwe wartości na żądanie
Generator produkuje wartości po jednej (yield), dopiero gdy o nie poprosisz

Generator jest funkcją która może zostać wstrzymana i wznowiona od miejsca w którym została wstrzymana. Generatory cechują się leniwą ewaluacją. Tworzą kolejne elementy dopiero w momencie odwołania się do generatora. Pozwala nam to wydajniej wykorzystywać pamięć operacyjną i zwracać z generatora nieskończoną liczbę elementów

Generatory nie tworzą całej zwracanej kolekcji od razu, tylko każda kolejna wartość jest generowana w momencie jej pobrania. To duża oszczędność dla pamięci, ponieważ w dowolnym momencie możemy przerwać pobieranie kolejnych wartości. Nie moglibysmy tego zrobić gdyby funkcja zwracała listę, choć sposób iteracji byłby taki sam. W przypadku generatorów w pamięci przechowywany jest tylko jeden aktualnie pobierany element a nie cała lista, co pozwala nam przetwarzać zbiory w zasadzie nieskończenie długie, czego nie moglibysmy zrobić w przypadku zwracania listy. Lista mogłaby przepełnić dostępną pamięć.

Z zagadnień podstawowych pamiętamy taką konstrukcję:

for x in range(10):
    print(x)

Konstrukcja ta pozwalała na iterowanie po kolejnych elementach zwracanych przez range. Istnieje możliwość tworzenia własnych mechanizmów tego typu. Przyjrzyjmy się poniższemu kodowi:

def elementy():
    yield 'element numer 1'
    yield 'element numer 2'
    yield 'element numer 3'
    yield 'element numer 4'

for e in elementy():
    print(e)

Powyżej widzimy bardzo prosty generator. Wynik jego działania przedstawia się następująco:

element numer 1
element numer 2
element numer 3
element numer 4

Moja funkcja "elementy" za pomocą słowa kluczowego "yield" podaje nam kolejne elementy. Słowo kluczowe „yield” odpowiada za przerwanie wykonania funkcji, zapisanie jej aktualnego stanu i zwrócenie kolejnej wartości. Jak widzimy w powyższym przykładzie, stan wykonania funkcji elementy był zapamiętywany i dlatego iterując po wyniku tej funkcji dostajemy kolejne podawane przez „yield” elementy.

W podobny sposób możemy utworzyć własną adaptację range podającą nam wartości co 10:

def myrange(n):
    for x in range(n):
        yield x*10

for x in myrange(10):
    print(x)

Idąc tym tropem możemy budować bardziej złożone konstrukcje. Poniżej przykład generatora który podaje tyle potęg kolejnych liczb ile otrzyma przez argument:

def potegi2(n):
    for x in range(1, n + 1):
        yield pow(2, x)

for p in potegi2(5):
    print(p)

Wynik działania na konsoli:

2
4
8
16
32

Nie zawsze chcemy przetworzyć cały zbiór jaki generator może nam zwrócić. Weźmy pod uwagę funkcję generującą która będzie nam zwracała kolejne wartości bez końca. W poniższym przypadku będą to kolejne dziesięci:

def dziesieci():
    i=1
    while True:
       yield i*10
       i+=1

Mogę teraz pojedynczo pobierać kolejne wartości korzystając z poniższej konstrukcji:

dz=dziesieci()
print( dz.__next__() )
print( dz.__next__() )
print( dz.__next__() )

Na konsoli dostaję dane:

10

20

30

Ten sam skutek mogę osiągnąć taką konstrukcją:

dz=dziesieci()
print(next(dz))
print(next(dz))
print(next(dz))

Generator może oddawać wartości różnych typów i generować je na różne sposoby, ważne by oddawać kolejne wartości za pomocą yield:

def poryRoku():
    pory = ['styczeń', 'luty', 'marzec', 'kwiecień', 'maj', 'czerwiec', 'lipiec', 'sierpień', 'wrzesień', 'październik',
            'listopad', 'grudzień']
    for e in pory:
        yield e


for p in poryRoku():
    print(p)

Powyzej generator zwracający nam nazwy kolejnych miesięcy. Kolejny przykład to generator kolejnych liczb parzystych:

def parzyste(n):
    for x in range(n + 1):
        yield 2 * x

for p in parzyste(10):
    print(p)

Generator kolejnych liter alfabetu:

def literki():
    for x in range(97,123):
        yield(chr(x))

for l in literki():
    print(l)

Teraz nieco bardziej praktyczny przykład. Generator który czyta plik csv, rozbija każdą z linii csv na listę wg podanego przez argument rozdzielacza. Na potrzeby tego przykładu stworzyłem plik o nazwie "plik.csv" i umieściłem w nim następujące dane:

1;Artur
2;Krzysztof
3;Zenon
4;Marcin
5;Andrzej

Dodajemy generator i wywołujemy go:

def rozbijacz_csv(np,r):
    plik=open(np,encoding='utf-8')
    while True:
        linia=plik.readline()
        if not linia:
            break
        yield linia.strip().split(r)

rc=rozbijacz_csv('plik.csv',';')
print(next(rc))
print(next(rc))

Wynik na konsoli:

['1', 'Artur']
['2', 'Krzysztof']

Zwróć uwagę że nie użyłem konstrukcji typu "for linia in plik.readlines(): " tylko wczytuję kolejne linie jedna po drugiej. Robię tak dlatego, że "readlines()" wczytuje od razu całą zawartość pliku, co mogłoby skończyć się przepełnieniem pamięci w przypadku bardzo dużego pliku.

Szkolenie Python od podstaw, przez analizę danych, do machine learning w JSystems

Ten kurs daje Ci solidne podstawy Pythona. Jeśli chcesz pójść dalej i pod okiem trenera dojść od pierwszej linijki kodu do własnego modelu uczenia maszynowego, sprawdź nasze pięciodniowe szkolenie Python od podstaw, przez analizę danych, do machine learning. Pierwsze trzy dni to Python w praktyce: typy i kolekcje, instrukcje warunkowe i pętle, funkcje, moduły i pakiety, pliki tekstowe, usługi sieciowe oraz praca z bazą PostgreSQL. Czwarty dzień to analiza danych w pandas (wczytywanie CSV, TSV i XLS, filtrowanie, agregacje, statystyki) i wizualizacja w Matplotlib, Seaborn oraz Plotly. Piąty dzień to uczenie maszynowe w scikit-learn: regresja liniowa i logistyczna, drzewa decyzyjne, las losowy, XGBoost, walidacja predykcji, dobór hiperparametrów i przeciwdziałanie przeuczeniu. Szkolenie ma terminy gwarantowane, czyli odbywa się niezależnie od wielkości grupy.

Szkolenie Python od podstaw, przez analizę danych, do machine learning

To szkolenie może być dofinansowane dla Ciebie z KFS lub BUR.

★★★★★Średnia ocena naszych szkoleń w Google: 5/5

Najczęściej zadawane pytania

Czym jest generator w Pythonie?
Generator to funkcja, którą można wstrzymać i wznowić od miejsca zatrzymania. Kolejne wartości oddaje słowem kluczowym yield, które przerywa wykonanie, zapisuje stan funkcji i zwraca następną wartość. Dzięki temu iterowanie po wyniku podaje kolejne elementy jeden po drugim.
Na czym polega leniwa ewaluacja generatorów?
Generator tworzy każdą kolejną wartość dopiero w momencie jej pobrania, a nie całą kolekcję od razu. W pamięci przechowywany jest tylko aktualnie pobierany element, dlatego można przetwarzać zbiory praktycznie nieskończone bez ryzyka przepełnienia pamięci.
Czym generator różni się od funkcji zwracającej listę?
Funkcja zwracająca listę buduje w pamięci całą kolekcję naraz, co przy dużych zbiorach może przepełnić pamięć. Generator trzyma tylko jeden element w danej chwili i pozwala przerwać pobieranie w dowolnym momencie, mimo że sposób iteracji jest podobny.
Jak pobrać z generatora pojedynczą wartość?
Pojedynczą wartość pobierzesz wbudowaną funkcją next, przekazując jej obiekt generatora, albo równoważnie metodą dunder next. Każde wywołanie zwraca kolejną wartość i wznawia funkcję od miejsca ostatniego yield.
Dlaczego przy czytaniu dużego pliku warto użyć generatora?
Wczytanie całego pliku metodą readlines ładuje jego zawartość do pamięci naraz, co przy bardzo dużym pliku grozi jej przepełnieniem. Generator czytający kolejne linie metodą readline pobiera je jedna po drugiej, dzięki czemu w pamięci jest tylko bieżący wiersz.

Komentarze (0)

Musisz być zalogowany by móc dodać komentarz. Zaloguj się przez Google

Brak komentarzy...