Найти в Дзене

Введение в yield from в Python: Упрощение работы с генераторами

Оглавление

Генераторы в Python — мощный инструмент для создания итераторов без необходимости реализовывать классы с методами __iter__ и __next__. Они позволяют генерировать значения "на лету" с помощью ключевого слова yield. Однако при работе с вложенными генераторами код может стать громоздким. Здесь на помощь приходит конструкция yield from, добавленная в Python 3.3. Она упрощает делегирование выполнения субгенераторам, делает код чище и расширяет возможности генераторов.

Что такое yield from?

yield from — это синтаксическая конструкция, которая позволяет генератору делегировать часть своей работы другому генератору (или любому итерируемому объекту). Это избавляет от необходимости вручную перебирать элементы субгенератора, а также обеспечивает прямую передачу значений и исключений между вызывающим кодом и субгенератором.

Основные возможности:

1. Делегирование выполнения другому генератору.

2. Передача данных в субгенератор через send().

3. Обработка исключений на уровне внешнего генератора.

4. Получение возвращаемого значения субгенератора (с Python 3.3).

Пример без yield from

Представим, что нам нужно объединить значения из нескольких генераторов:

def generator1():
....yield from [1, 2, 3]
def generator2():
....for value in generator1():
........yield value
# Использование
for num in generator2():
....print(num)
# Вывод: 1 2 3

Здесь generator2 вручную перебирает generator1. С yield from код упрощается:

def generator2():
....yield from generator1()

Как работает yield from?

Когда интерпретатор встречает yield from subgen(), происходит следующее:

1. Вызывается субгенератор subgen().

2. Значения из subgen() напрямую передаются в вызывающий код.

3. При вызове send(), throw() или close() на внешний генератор, эти методы делегируются субгенератору.

4. Когда субгенератор завершается, его возвращаемое значение (если есть) становится результатом выражения yield from.

Схема взаимодействия:

Вызывающий код <--> Внешний генератор (через yield from) <--> Субгенератор

Расширенные возможности yield from

1. Передача данных в субгенератор

Субгенераторы могут принимать данные через send():

def subgen():
....while True:
........x = yield
........print(f"Получено: {x}")
def main_gen():
....yield from subgen()
gen = main_gen()
next(gen) # Инициализация
gen.send(10) # Выведет: "Получено: 10"

2. Обработка исключений

Исключения, брошенные в вызывающем коде, передаются в субгенератор:

def subgen():
....try:
........yield 42
....except ValueError:
........print("Ошибка обработана!")
def main_gen():
....yield from subgen()
gen = main_gen()
next(gen) # Получаем 42
gen.throw(ValueError) # Выведет: "Ошибка обработана!"

3. Получение возвращаемого значения

Возвращаемое значение субгенератора можно сохранить:

def subgen():
....yield 1
....yield 2
....return "Готово!"
def main_gen():
....result = yield from subgen()
....print(f"Результат: {result}")
for value in main_gen():
....print(value)
# Вывод:
# 1
# 2
# Результат: Готово!

Использование в асинхронном программировании

В асинхронных фреймворках, таких как asyncio, yield from применялся для ожидания результатов корутин до появления ключевых слов async/await в Python 3.5. Пример:

import asyncio
@asyncio.coroutine
def async_task():
....yield from asyncio.sleep(1)
....print("Завершено")
loop = asyncio.get_event_loop()
loop.run_until_complete(async_task())

Ограничения и советы

1. Итерируемый объект: Субгенератор должен быть итерируемым. Использование yield from с неитерируемым объектом вызовет ошибку.

2. Порядок выполнения: yield from "блокирует" внешний генератор до полного выполнения субгенератора.

3. Совместимость: yield from доступен только с Python 3.3 и выше.

Заключение

Конструкция yield from — это мощный инструмент для:

- Упрощения кода с вложенными генераторами.

- Организации двусторонней коммуникации между генераторами.

- Обработки исключений и получения результатов выполнения субгенераторов.

Она особенно полезна при работе с деревьями или цепочками генераторов, а также в асинхронном программировании. Освоение yield from позволяет писать более чистый, эффективный и поддерживаемый Python-код.

Подписывайтесь:

Телеграм https://t.me/lets_go_code
Канал "Просто о программировании"
https://dzen.ru/lets_go_code