Генераторы в Python
В этой статье объясняются генераторы в Python.
YouTube Video
Генераторы в Python
Обзор
Генераторы в Python — это вид итераторов и мощный инструмент для эффективной обработки повторяющихся операций. Они позволяют писать памяти-экономящий код при работе с большими объемами данных.
Что такое генератор?
Генератор в Python — это специальная функция, которая выдаёт по одному значению за раз, определяемая с помощью ключевого слова yield
. Её особенностью является то, что выполнение прерывается с сохранением состояния и может быть возобновлено позже.
Основы yield
yield
— это ключевое слово, которое возвращает значение и приостанавливает выполнение функции одновременно.
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
- При вызове такая функция возвращает объект-генератор, который отдаёт значения по одному за раз.
- Если вызвать
next()
, когда нет следующего значения, произойдет ошибкаStopIteration
.
next()
и 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")
- Явно обрабатывая ошибку
StopIteration
таким образом, вы можете определить, когда генератор завершил работу.
send(value)
Вызов send(value)
возобновляет выполнение генератора и отправляет значение value
на позицию выражения yield
. Отправленное значение может быть получено на стороне генератора как возвращаемое значение выражения yield
. При первом вызове нельзя отправлять ничего, кроме None
с помощью send(value)
, поэтому необходимо использовать next()
или 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
- При вызове
send(10)
выражениеyield
в генераторе становится выражением, возвращающим 10, и 10 присваивается переменнойx
.
throw()
Вызов throw
возобновляет выполнение генератора и вызывает исключение в месте приостановки на выражении yield
. Вы можете обработать исключение внутри генератора, чтобы продолжить выполнение. Если исключение не перехвачено, оно распространяется наружу и генератор завершает выполнение.
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"
- В этом коде вызывается
throw
, чтобы ввести исключение в генератор. На стороне генератора исключение обрабатывается и возвращается значениеrecovered
.
close()
Вызов close()
завершает выполнение генератора. Внутри генератора можно выполнить очистку с помощью блока finally
. Вызов next()
или send()
после close()
вызывает ошибку 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...
- Этот код показывает, что вызов
close()
завершает выполнение генератора и запускает процесс очистки в блокеfinally
.
yield from
yield from
— это синтаксис, используемый для делегирования подгенератору. Это простой способ вызвать другой генератор внутри генератора и передать все его значения во внешний контекст.
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]
- Этот код при помощи
yield from
делегирует все значения от подгенератора внешнему генератору, а затем возвращает3
.
Связь с итераторами
Генераторы внутри реализуют методы __iter__()
и __next__()
, что делает их видом итераторов. Поэтому они полностью совместимы с итерируемыми операциями, такими как циклы for
.
Использование с циклами for
В Python цикл for
внутренне использует next()
, чтобы автоматически получать значения.
1def simple_generator():
2 yield 1
3 yield 2
4 yield 3
5
6for value in simple_generator():
7 print(value)
Таким образом, обработка StopIteration
происходит автоматически.
Создание бесконечных генераторов
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
Возможно создавать бесконечные циклы, однако использовать их нужно осторожно.
Генераторные выражения
Генераторные выражения, записываемые в скобках, позволяют создавать генераторы с синтаксисом, похожим на списковые включения.
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)
В отличие от списковых включений, они не загружают все элементы в память сразу, что делает их более эффективными по памяти.
Обработка ошибок в генераторах
Внутри генератора могут возникать исключения. В таких случаях используется try-except
так же, как в обычном коде Python.
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
В этом примере выполняется правильная обработка ошибки при делении на ноль.
Трассировка стека генератора
Если внутри генератора происходит исключение, оно будет вызвано при возобновлении работы генератора.
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)
- Этот генератор сначала возвращает
1
. Ошибка, возникшая при возобновлении, перехватывается и выводится как сообщение об ошибке.
Примеры использования генераторов
Чтение файла построчно (подходит для больших файлов)
1def read_large_file(filepath):
2 with open(filepath, 'r') as f:
3 for line in f:
4 yield line.strip()
- Эта функция построчно читает текстовый файл с помощью итератора, удаляет пробелы в начале и в конце каждой строки и возвращает их в виде генератора, что позволяет обрабатывать большие файлы с низким потреблением памяти.
Генератор для последовательности Фибоначчи
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)
- Этот код использует генератор для последовательной генерации чисел Фибоначчи, меньших верхнего предела, и выводит их с помощью цикла
for
.
Сценарии применения
Генераторы также могут использоваться в следующих случаях.
- Последовательная обработка больших CSV или лог-файлов
- Постраничная обработка API
- Обработка потоковых данных (например, Kafka, IoT-устройства)
Резюме
Понятие | Ключевой момент |
---|---|
yield |
Приостанавливает выполнение и возвращает значение |
Функция-генератор | Функция, содержащая yield и возвращающая итератор при вызове |
Преимущества | Экономно используется память, идеально для работы с большими наборами данных |
Генераторное выражение | Позволяет писать лаконичный синтаксис вроде (x for x in iterable) |
Используя генераторы, вы можете эффективно обрабатывать большие объемы данных, экономя память и сохраняя ваш код лаконичным.
Вы можете следовать этой статье, используя Visual Studio Code на нашем YouTube-канале. Пожалуйста, также посмотрите наш YouTube-канал.