Blog JSystems - uwalniamy wiedzę!

Szukaj

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.

Przyjdź do nas na szkolenie z języka Python! Mamy szereg szkoleń w ofercie, od podstawowych po aplikacje webowe z użyciem Django, analizę danych, tesowanie, machine learning i wiele innych. Sprawdź dostępne szkolenia Python

Komentarze (0)

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

Brak komentarzy...

Zapisz się do newslettera aby otrzymywać najnowsze świeżynki pojawiające się na blogu! Zapisz się do newslettera