Generatory

Python SELF PL
Poziom 13 , Lekcja 4
Dostępny

3.1 Wprowadzenie do generatorów

Generatory to funkcje, które zwracają obiekt-iterator. Te iteratory generują wartości na żądanie, co pozwala na obsługę potencjalnie dużych zbiorów danych bez konieczności ładowania ich w całości do pamięci.

Istnieje kilka sposobów tworzenia generatorów, poniżej omówimy te najpopularniejsze.

Generatory oparte na funkcjach

Generatory tworzy się za pomocą słowa kluczowego yield wewnątrz funkcji. Kiedy funkcja z yield jest wywoływana, zwraca obiekt-generator, ale nie wykonuje kodu wewnątrz funkcji od razu. Zamiast tego wykonywanie zostaje zatrzymane na wyrażeniu yield i wznawia się przy każdym wywołaniu metody __next__() obiektu-generatora.


def count_up_to(max):
    count = 1
    while count <= max:
        yield count
        count += 1
        
counter = count_up_to(5)
print(next(counter))  # Wyjście: 1
print(next(counter))  # Wyjście: 2
print(next(counter))  # Wyjście: 3
print(next(counter))  # Wyjście: 4
print(next(counter))  # Wyjście: 5

Jeśli w funkcji znajduje się operator yield, Python zamiast tradycyjnego wykonania funkcji tworzy obiekt-generator, który zarządza stanem wykonania funkcji.

Wyrażenia generatorowe

Wyrażenia generatorowe są podobne do wyrażeń listowych (List Comprehension), ale tworzone są za pomocą nawiasów okrągłych zamiast kwadratowych. Zwracają również obiekt-generator.


squares = (x ** 2 for x in range(10))

print(next(squares))  # Wyjście: 0
print(next(squares))  # Wyjście: 1
print(next(squares))  # Wyjście: 4

Która metoda bardziej Ci się podoba?

3.2 Zalety generatorów

Efektywne wykorzystanie pamięci

Generatory obliczają wartości w locie, co pozwala na przetwarzanie dużych danych bez ich pełnego ładowania do pamięci. To czyni generatory idealnym wyborem do pracy z dużymi zbiorami danych lub strumieniami danych.


def large_range(n):
    for i in range(n):
        yield i
        
for value in large_range(1000000):
    # Przetwarzamy wartości pojedynczo
    print(value)

Leniwe obliczenia

Generatory wykonują leniwe obliczenia, co oznacza, że obliczają wartości tylko wtedy, kiedy jest to konieczne. Pozwala to uniknąć zbędnych obliczeń i poprawia wydajność.


def fibonacci():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b
        
fib = fibonacci()
for _ in range(10):
    print(next(fib))

Wygoda składni

Generatory oferują wygodną składnię do tworzenia iteratorów, co upraszcza pisanie i czytanie kodu.

3.3 Użycie generatorów

Przykłady użycia generatorów w bibliotece standardowej

Wiele funkcji w bibliotece standardowej Pythona używa generatorów. Na przykład, funkcja range() zwraca obiekt-generator, który generuje sekwencję liczb.


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

Tak, świat już nigdy nie będzie taki sam.

Tworzenie nieskończonych sekwencji

Generatory pozwalają tworzyć nieskończone sekwencje, które mogą być przydatne w różnych scenariuszach, takich jak generowanie nieskończonych strumieni danych.


def natural_numbers():
    n = 1
    while True:
        yield n
        n += 1
        
naturals = natural_numbers()
for _ in range(10):
    print(next(naturals))

Użycie send() i close()

Obiekty-generatorów obsługują metody send() i close(), które pozwalają wysyłać wartości z powrotem do generatora i kończyć jego działanie.


def echo():
    while True:
        received = yield
        print(received)
        
e = echo()
next(e)  # Uruchamiamy generator
e.send("Cześć, świecie!")  # Wyjście: Cześć, świecie!
e.close()

3.4 Generatory w praktyce

Generatory i wyjątki

Generatory mogą obsługiwać wyjątki, co czyni je potężnym narzędziem do pisania bardziej odpornych programów.


def controlled_execution():
    try:
        yield "Start"
        yield "Praca"
    except GeneratorExit:
        print("Generator zamknięty")
        
gen = controlled_execution()
print(next(gen))  # Wyjście: Start
print(next(gen))  # Wyjście: Praca
gen.close()  # Wyjście: Generator zamknięty

Pracę z wyjątkami omówimy na kolejnych wykładach, ale myślę, że będzie dla ciebie przydatne wiedzieć, że generatory radzą sobie z nimi świetnie.

Zagnieżdżone generatory

Generatory mogą być zagnieżdżane, co pozwala na tworzenie złożonych struktur iteracyjnych.


def generator1():
    yield from range(3)
    yield from "ABC"
        
for value in generator1():
    print(value)

# Wyjście
0
1
2
A
B
C

Wyjaśnienie:

yield from: Ta konstrukcja służy do delegowania części operacji innemu generatorowi, co pozwala na uproszczenie kodu i poprawę jego czytelności.

Generatory a wydajność

Użycie generatorów może znacznie poprawić wydajność programów dzięki zmniejszeniu wykorzystania pamięci i bardziej efektywnemu wykonywaniu iteracji.

Przykład porównania list i generatorów


import time
import sys

def memory_usage(obj):
    return sys.getsizeof(obj)

n = 10_000_000

# Użycie listy
start_time = time.time()
list_comp = [x ** 2 for x in range(n)]
list_time = time.time() - start_time
list_memory = memory_usage(list_comp)

# Użycie generatora
start_time = time.time()
gen_comp = (x ** 2 for x in range(n))
gen_result = sum(gen_comp)  # Obliczamy sumę dla porównania wyników
gen_time = time.time() - start_time
gen_memory = memory_usage(gen_comp)

print(f"Lista:")
print(f"  Czas: {list_time:.2f} sec")
print(f"  Pamięć: {list_memory:,} bajtów")

print(f"\nGenerator:")
print(f"  Czas: {gen_time:.2f} sec")
print(f"  Pamięć: {gen_memory:,} bajtów")

Lista:
  Czas: 0.62 sec
  Pamięć: 89,095,160 bajtów

Generator:
  Czas: 1.13 sec
  Pamięć: 200 bajtów
1
Ankieta/quiz
Funkcje systemowe, poziom 13, lekcja 4
Niedostępny
Funkcje systemowe
Funkcje systemowe
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION