Генераторы в Python

Генераторы в 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-канал.

YouTube Video