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
StopIterationw 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żenieyieldw 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
throwjest 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)) # 1Moż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, infW 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.