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łądStopIteration
.
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żenieyield
w generatorze staje się wyrażeniem zwracającym 10, a 10 zostaje przypisane dox
.
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 blokufinally
.
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 zwraca3
.
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.