← Назад к вопросам

Что такое yield from?

1.0 Junior🔥 161 комментариев
#Другое

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

yield from

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

Основное назначение

yield from передаёт (делегирует) управление вложенному генератору, позволяя:

  1. Передавать значения от вложенного генератора напрямую
  2. Получать значения от функции, вызывающей генератор
  3. Обрабатывать исключения в вложенном генераторе
  4. Упростить код при работе с вложенными итерациями

Простой пример

# ❌ Без yield from — много кода
def chain_old(a, b):
    for x in a:
        yield x
    for x in b:
        yield x

# ✅ С yield from — компактно
def chain_new(a, b):
    yield from a
    yield from b

# Результат одинаковый
list1 = [1, 2, 3]
list2 = [4, 5, 6]
print(list(chain_old(list1, list2)))  # [1, 2, 3, 4, 5, 6]
print(list(chain_new(list1, list2)))  # [1, 2, 3, 4, 5, 6]

Работа с вложенными генераторами

# Генератор, который выдаёт значения
def sub_generator():
    yield 1
    yield 2
    yield 3

# Генератор, который делегирует другому
def main_generator():
    print("Старт")
    yield from sub_generator()
    print("Конец")

for value in main_generator():
    print(value)

# Результат:
# Старт
# 1
# 2
# 3
# Конец

Передача значений в генератор

yield from поддерживает двусторонний обмен данными с вложенным генератором:

# Генератор, который получает значения
def sub_gen():
    print("Подгенератор запущен")
    while True:
        value = yield  # Получить значение
        print(f"Подгенератор получил: {value}")
        if value is None:
            break

# Генератор, который передаёт значения
def delegator():
    print("Делегатор запущен")
    yield from sub_gen()
    print("Делегатор завершён")

# Использование
gen = delegator()
next(gen)  # Запуск
gen.send("Hello")  # Передать значение в подгенератор
gen.send("World")  # Ещё значение
try:
    gen.send(None)  # Завершить
except StopIteration:
    pass

# Результат:
# Делегатор запущен
# Подгенератор запущен
# Подгенератор получил: Hello
# Подгенератор получил: World
# Делегатор завершён

Возвращаемое значение

yield from передаёт возвращаемое значение от вложенного генератора:

def sub_generator():
    yield 1
    yield 2
    yield 3
    return "Готово!"  # Возвращаемое значение

def main_generator():
    # yield from возвращает результат подгенератора
    result = yield from sub_generator()
    print(f"Подгенератор вернул: {result}")
    yield "Конец"

for value in main_generator():
    print(value)

# Результат:
# 1
# 2
# 3
# Подгенератор вернул: Готово!
# Конец

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

yield from автоматически передаёт исключения в вложенный генератор:

def sub_gen():
    try:
        yield 1
        yield 2
        yield 3
    except ValueError as e:
        print(f"Подгенератор поймал ошибку: {e}")
        yield "После ошибки"

def main_gen():
    yield from sub_gen()

# Использование
gen = main_gen()
print(next(gen))  # 1
print(next(gen))  # 2
try:
    gen.throw(ValueError("Тестовая ошибка"))
except StopIteration:
    pass

# Результат:
# 1
# 2
# Подгенератор поймал ошибку: Тестовая ошибка
# После ошибки

Практический пример: flatten

# Развернуть вложенный список
def flatten(nested_list):
    for item in nested_list:
        if isinstance(item, list):
            # Рекурсивно развернуть вложенные списки
            yield from flatten(item)
        else:
            yield item

nested = [1, [2, 3, [4, 5]], 6, [7, [8, 9]]]
print(list(flatten(nested)))  # [1, 2, 3, 4, 5, 6, 7, 8, 9]

Асинхронные генераторы

async def async_sub_gen():
    for i in range(3):
        yield i

async def async_main_gen():
    # Асинхронная версия yield from
    async for value in async_sub_gen():
        yield value * 2

async def main():
    async for value in async_main_gen():
        print(value)  # 0, 2, 4

import asyncio
asyncio.run(main())

Эквивалент без yield from

# С yield from
def with_yield_from(a, b):
    yield from a
    yield from b

# Эквивалент без yield from
def without_yield_from(a, b):
    for item in a:
        yield item
    for item in b:
        yield item

# Идентичны по функциональности
print(list(with_yield_from([1, 2], [3, 4])))     # [1, 2, 3, 4]
print(list(without_yield_from([1, 2], [3, 4])))  # [1, 2, 3, 4]

Когда использовать

# ✅ Идеально для:

# 1. Цепочка генераторов
def process_files(*file_paths):
    for path in file_paths:
        yield from read_file(path)

# 2. Рекурсивные структуры
def traverse_tree(node):
    yield node.value
    for child in node.children:
        yield from traverse_tree(child)

# 3. Делегирование логики
def pipeline(data, *transformations):
    result = data
    for transform in transformations:
        yield from transform(result)

# ❌ Избегать для:
# - Простых циклов (используй обычный yield)
# - Когда нужна особая обработка каждого значения

Производительность

yield from примерно на 5-10% быстрее обычного цикла с yield, так как избегает создания промежуточного frame в стеке вызовов:

import timeit

def gen1():
    for i in range(1000):
        yield i

def with_yield_from():
    yield from gen1()

def without_yield_from():
    for x in gen1():
        yield x

print(timeit.timeit(lambda: list(with_yield_from()), number=10000))
# ~0.85 сек

print(timeit.timeit(lambda: list(without_yield_from()), number=10000))
# ~0.92 сек

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