Generatory w Pythonie

Generatory w Pythonie

Ten artykuł wyjaśnia generatory w Pythonie.

YouTube Video

Generatory w Pythonie

Przegląd

Generatory w Pythonie są typem iteratora oraz potężną funkcją umożliwiającą wydajne przetwarzanie powtarzalnych operacji. Pozwalają pisać wydajny pod względem pamięci kod, gdy pracujesz z dużą ilością danych.

Czym jest generator?

Generator w Pythonie to specjalna funkcja, która generuje po jednej wartości naraz, definiowana przy użyciu słowa kluczowego yield. Charakteryzuje się tym, że zatrzymuje wykonanie, zachowując swój stan, i może zostać wznowiony później.

Podstawy yield

yield to słowo kluczowe, które zwraca wartość i jednocześnie wstrzymuje wykonywanie funkcji.

 1def simple_generator():
 2    yield 1
 3    yield 2
 4    yield 3
 5
 6gen = simple_generator()
 7
 8print(next(gen))  # 1
 9print(next(gen))  # 2
10print(next(gen))  # 3
  • Po wywołaniu taka funkcja zwraca obiekt generatora, który zwraca wartości pojedynczo.
  • Jeśli wywołasz next(), gdy nie ma kolejnej wartości, wystąpi błąd StopIteration.

next() i StopIteration

 1def simple_generator():
 2    yield 1
 3    yield 2
 4    yield 3
 5
 6gen = simple_generator()
 7
 8try:
 9    while True:
10        value = next(gen)
11        print(value)
12except StopIteration:
13    print("Finished")
  • Poprzez jawne obsłużenie błędu StopIteration w ten sposób, możesz wykryć, kiedy generator się zakończył.

send(value)

Wywołanie send(value) wznawia generator i przekazuje value do miejsca wyrażenia yield. Wysłana wartość może zostać odebrana po stronie generatora jako zwracana wartość wyrażenia yield. Przy pierwszym wywołaniu nie można przesłać niczego poza None za pomocą send(value), więc należy użyć next() lub send(None).

 1def gen():
 2    x = yield 1
 3    print(f"x = {x}")
 4    y = yield 2
 5    print(f"y = {y}")
 6
 7g = gen()
 8print(next(g))       # -> 1 (value from yield 1)
 9print(g.send(10))    # -> x = 10, 2 (value from yield 2)
10print(g.send(20))    # -> y = 20, StopIteration occurs
  • Dzięki send(10) wyrażenie yield w generatorze staje się wyrażeniem zwracającym 10, a 10 zostaje przypisane do x.

throw()

Wywołanie throw wznawia generator i zgłasza wyjątek w miejscu zatrzymanego wyrażenia yield. Możesz obsłużyć wyjątek wewnątrz generatora, aby kontynuować przetwarzanie. Jeśli wyjątek nie zostanie przechwycony, propaguje się na zewnątrz i generator kończy działanie.

 1def gen():
 2    try:
 3        yield 1
 4    except ValueError as e:
 5        print(f"Caught: {e}")
 6        yield "recovered"
 7
 8g = gen()
 9print(next(g))   # -> 1
10print(g.throw(ValueError("boom")))  # -> Caught: boom, "recovered"
  • W tym kodzie throw jest wywoływane, aby wstrzyknąć wyjątek do generatora. Po stronie generatora wyjątek jest obsługiwany i zwracana jest wartość recovered.

close()

Wywołanie close() kończy działanie generatora. Wewnątrz generatora można przeprowadzić sprzątanie przy użyciu bloku finally. Wywołanie next() lub send() po wywołaniu close() zgłasza błąd StopIteration.

1def gen():
2    try:
3        yield 1
4    finally:
5        print("Cleaning up...")
6
7g = gen()
8print(next(g))  # -> 1
9g.close()       # -> Cleaning up...
  • Ten kod pokazuje, że wywołanie close() kończy działanie generatora i uruchamia proces sprzątania w bloku finally.

yield from

yield from to składnia używana do delegowania do subgeneratora. Jest to prosty sposób na wywołanie innego generatora wewnątrz generatora i przekazanie wszystkich jego wartości na zewnątrz.

1def sub_gen():
2    yield 1
3    yield 2
4
5def main_gen():
6    yield from sub_gen()
7    yield 3
8
9print(list(main_gen()))  # -> [1, 2, 3]
  • Ten kod przekazuje wszystkie wartości z subgeneratora do zewnętrznego generatora za pomocą yield from, a następnie zwraca 3.

Związek z iteratorami

Generatory wewnętrznie implementują __iter__() oraz __next__(), dzięki czemu są typem iteratora. Dlatego są w pełni kompatybilne z operacjami iteracyjnymi, takimi jak pętle for.

Integracja z pętlami for

W Pythonie pętla for wewnętrznie używa next(), aby automatycznie pobierać wartości.

1def simple_generator():
2    yield 1
3    yield 2
4    yield 3
5
6for value in simple_generator():
7    print(value)

Dzięki tej metodzie obsługa StopIteration jest również automatyczna.

Tworzenie nieskończonych generatorów

1def count_up(start=0):
2    while True:
3        yield start
4        start += 1
5
6counter = count_up()
7print(next(counter))  # 0
8print(next(counter))  # 1

Możliwe jest stworzenie nieskończonych pętli, ale należy zachować ostrożność przy ich użyciu.

Wyrażenia generatorów

Wyrażenia generatorów, pisane w nawiasach, pozwalają definiować generatory z użyciem składni podobnej do list comprehensions.

1# List comprehension (generates the entire list at once)
2squares_list = [x**2 for x in range(5)]
3print(squares_list)
4
5# Generator expression
6squares_gen = (x**2 for x in range(5))
7for square in squares_gen:
8    print(square)

W przeciwieństwie do list comprehensions, nie ładują wszystkich elementów do pamięci naraz, co czyni je bardziej wydajnymi pamięciowo.

Obsługa błędów w generatorach

Wyjątki mogą wystąpić wewnątrz generatora. W takich przypadkach używasz try-except tak jak w zwykłym kodzie Pythona.

 1def safe_divide_generator(numbers, divisor):
 2    """Yields results of dividing numbers by a given divisor safely."""
 3    for number in numbers:
 4        try:
 5            yield number / divisor  # Attempt to divide and yield result.
 6        except ZeroDivisionError:
 7            yield float('inf')  # Return infinity if division by zero occurs.
 8
 9# Example usage
10numbers = [10, 20, 30]
11gen = safe_divide_generator(numbers, 0)  # Create generator with divisor as 0.
12for value in gen:
13    print(value)  # Output: inf, inf, inf

W tym przykładzie przeprowadzona jest odpowiednia obsługa błędów w przypadku dzielenia przez zero.

Stack trace generatora

Jeśli wyjątek wystąpi wewnątrz generatora, zostanie on zgłoszony przy wznowieniu działania generatora.

 1def error_generator():
 2    """A generator that yields values and raises an error."""
 3    yield 1
 4    raise ValueError("An error occurred")  # Raise a ValueError intentionally.
 5    yield 2
 6
 7gen = error_generator()
 8print(next(gen))       # Output: 1 (first value yielded)
 9try:
10    print(next(gen))   # Attempt to get the next value, which raises an error
11except ValueError as e:
12    print(e)           # Output: An error occurred (exception message is printed)
  • Ten generator najpierw zwraca 1. Błąd zgłoszony podczas wznawiania jest przechwytywany i wyświetlany jako komunikat o błędzie.

Przykłady użycia generatorów

Odczytywanie pliku linia po linii (odpowiednie dla dużych plików)

1def read_large_file(filepath):
2    with open(filepath, 'r') as f:
3        for line in f:
4            yield line.strip()
  • Ta funkcja czyta plik tekstowy linia po linii za pomocą iteratora, usuwa białe znaki z każdej linii i zwraca ją jako generator, co umożliwia przetwarzanie dużych plików przy niskim zużyciu pamięci.

Generator ciągu Fibonacciego

1def fibonacci(limit):
2    a, b = 0, 1
3    while a < limit:
4        yield a
5        a, b = b, a + b
6
7for n in fibonacci(100):
8    print(n)
  • Ten kod używa generatora do sekwencyjnego generowania liczb Fibonacciego mniejszych niż górny limit i wypisuje je za pomocą pętli for.

Przypadki użycia

Generatory mogą być również używane w następujących scenariuszach.

  • Sekwencyjne przetwarzanie dużych plików CSV lub logów
  • Paginacja API
  • Przetwarzanie danych strumieniowych (np. Kafka, urządzenia IoT)

Podsumowanie

Koncepcja Kluczowa uwaga
yield Wstrzymuje i zwraca wartość
Funkcja generatora Funkcja, która zawiera yield i zwraca iterator po wywołaniu
Zalety Wydajne pamięciowo i idealne do przetwarzania dużych zbiorów danych
Wyrażenie generatora Pozwala na zwięzłą składnię, taką jak (x for x in iterable)

Korzystając z generatorów, możesz wydajnie przetwarzać duże zbiory danych, oszczędzając pamięć i zachowując zwięzłość kodu.

Możesz śledzić ten artykuł, korzystając z Visual Studio Code na naszym kanale YouTube. Proszę również sprawdzić nasz kanał YouTube.

YouTube Video