Contents

Praca z generatorami w Pythonie

Jeśli czytasz wiersze z pliku dziennika lub przetwarzasz długą listę elementów, jedną z opcji jest załadowanie całych danych do pamięci. Takie podejście może jednak zużywać dużo pamięci i obniżać wydajność. Generatory oferują cenne rozwiązanie.

Generatory eliminują konieczność jednoczesnego ładowania ogromnych ilości danych do pamięci. Są one szczególnie korzystne w sytuacjach obejmujących duże zbiory danych, nieskończone serie lub wszelkie okoliczności, które kładą nacisk na zarządzanie pamięcią.

Czym są generatory?

/pl/images/wind-energy-generator.jpg

Generator można zdefiniować jako szczególny rodzaj funkcji, która umożliwia iteracyjne przetwarzanie serii elementów. W przeciwieństwie do tradycyjnych funkcji, które dostarczają cały zestaw danych za jednym razem, generatory stopniowo wytwarzają poszczególne komponenty na żądanie. W związku z tym są one bardzo skuteczne, gdy mamy do czynienia z rozległymi lub nieskończonymi zbiorami informacji.

Standardowa funkcja Pythona jest zwykle zaprojektowana do obliczania pojedynczej wartości i zwracania jej, podczas gdy funkcja generatora działa na zasadzie iteracji. Zamiast obliczać wartość i zwracać ją za jednym zamachem, funkcja generatora daje wiele wartości w czasie poprzez serię przerw i wznowień jej wykonywania.

Generowanie funkcjonalności w językach programowania często wiąże się z określeniem, w jaki sposób dane zostaną wygenerowane lub wykonane. Istnieje zasadnicza różnica między funkcjami standardowymi a funkcjami generatora w odniesieniu do ich podejścia do dostarczania wyników. Zwykłe funkcje zazwyczaj wykorzystują słowo kluczowe “return” jako sposób wyprowadzania danych, podczas gdy funkcje generatora polegają w tym celu na instrukcji “yield”.

Jak utworzyć generator

Aby skonstruować funkcję generatora, zamiast tradycyjnej instrukcji return, należy użyć instrukcji yield znajdującej się w ciele funkcji. Słowo kluczowe yield służy nie tylko jako instrukcja dla funkcji do wygenerowania wyniku, ale także umożliwia zachowanie jej bieżącego stanu, ułatwiając w ten sposób potencjalne wznowienie.

Oczywiście, oto przykład podstawowej funkcji generatora w Pythonie:

 def numeric_generator():
  yield 1
  yield 2
  yield 3

gen = numeric_generator()

Ta konkretna funkcja, po wykonaniu, generuje sekwencję liczb w zakresie od 1 do 3.

Instrukcja yield służy unikalnemu celowi w programowaniu funkcjonalnym, umożliwiając zawieszenie i wznowienie wykonywania funkcji przy jednoczesnym zachowaniu jej bieżącego stanu, w tym wszelkich lokalnie zdefiniowanych zmiennych, dla kolejnych wywołań. Pozwala to na płynną kontynuację obliczeń bez konieczności jawnego ponownego uruchamiania lub dodatkowej księgowości.

Przechowywanie funkcji generatora w zmiennej skutkuje utworzeniem obiektu generatora, który można wykorzystać do różnych operacji.

/pl/images/generator-object.jpg

Praca z generatorami

Generatory oferują wszechstronny zakres zastosowań w różnych kontekstach, w tym są wykorzystywane zarówno w pętlach for, jak i listach, a także w bardziej rozbudowanych strukturach iteracyjnych. Dodatkowo, generatory mogą być wykorzystywane jako parametry wejściowe dla wielu funkcji.

Po skonstruowaniu generatora można zastosować konstrukcję pętli znaną jako “pętla for”, aby iteracyjnie przeglądać jego dane wyjściowe. Pozwala to na systematyczne przetwarzanie każdego elementu sekwencji wygenerowanej przez funkcję bez konieczności ręcznego uzyskiwania do nich dostępu i manipulowania nimi indywidualnie.

 for i in numeric_generator():
  print(i)

Można również wykorzystać funkcję subsequent do sekwencyjnego uzyskiwania wartości:

 print(next(gen)) # 1
print(next(gen)) # 2
print(next(gen)) # 3

Dzięki takiemu podejściu uzyskuje się większy wpływ na jednostkę generującą.

/pl/images/numeric-generator-in-action.jpg

Generatory posiadają zdolność do utrzymywania swojego stanu wewnętrznego. Każde wystąpienie słowa kluczowego yield w ramach funkcji służy jako okazja dla generatora do wstrzymania postępu i zarejestrowania bieżącej pozycji. Po wywołaniu metody next() na obiekcie generatora, kontrola jest przekazywana z powrotem do poprzedniej instrukcji yield , skutecznie wznawiając wykonywanie w tym konkretnym punkcie.

Można również przesyłać dane do generatora za pomocą metody send() , umożliwiając dostarczanie wartości.

 def generator_with_send():
    # First yield: Receive a value
    x = yield
    print(f"Received: {x}")

    # Second yield: Receive another value
    y = yield
    print(f"Received: {y}")

    # Third yield: Yield the sum
    yield x \\+ y

gen = generator_with_send()

# Start generator and reach first yield
next(gen)

# Send 10 into generator, received at first yield
result = gen.send(10)

# Send 5 into generator, received at second yield
result = gen.send(5)

# Print result of third yield
print(result) 

Metoda send() w generatorach Pythona zapewnia mechanizm pobierania wartości wyjściowych i kontrolowania przepływu wykonania poprzez wysyłanie wartości z powrotem do funkcji generatora. Technika ta może być przydatna w sytuacjach, w których trzeba wstrzymać wykonywanie generatora lub napisać bardziej złożone programy współpracujące, które obejmują wiele wywołań funkcji generatora.

/pl/images/sending-values-to-generators.jpg

Korzystanie z wyrażeń generatora

Wyrażenia generatora oferują skuteczny sposób tworzenia prostego, bezimiennego generatora za pomocą skróconej składni, która wykorzystuje nawiasy zamiast nawiasów. Pod wieloma względami przypominają one wyrażenia listowe, ale wykazują odrębne cechy, które odróżniają je od ich odpowiedników.

Oto przykład:

 gen = (i**2 for i in range(10))

for x in gen:
  print(x)

Kod konstruuje obiekt generatora, który wytwarza kwadraty liczb całkowitych w zakresie od 0 do określonej górnej granicy, przy użyciu wyrażenia generatora. Takie podejście jest szczególnie przydatne w sytuacjach, w których tylko część danych wyjściowych musi zostać wygenerowana w danym momencie, ponieważ pozwala na wydajne i elastyczne tworzenie wartości na żądanie.

/pl/images/generator-expressions.jpg

Używanie generatorów do przetwarzania danych

Generatory Pythona oferują eleganckie rozwiązanie do opisywania strumieni danych przy jednoczesnym zminimalizowaniu zużycia pamięci. Opanowując ich wykorzystanie, programiści mogą z łatwością skutecznie radzić sobie ze skomplikowanymi zadaniami przetwarzania danych.

Podczas pracy z rozległymi zbiorami danych warto rozważyć wykorzystanie generatorów ze względu na ich zdolność do obsługi żmudnych zadań obliczeniowych przy jednoczesnym zachowaniu zwinnego i usprawnionego środowiska kodowania.