Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
yield from
yield from — это синтаксический сахар в Python (введён в PEP 380), который позволяет делегировать итерацию вложенному генератору. Это удобный способ перенаправить значения от одного генератора другому, избежав написания цикла.
Основное назначение
yield from передаёт (делегирует) управление вложенному генератору, позволяя:
- Передавать значения от вложенного генератора напрямую
- Получать значения от функции, вызывающей генератор
- Обрабатывать исключения в вложенном генераторе
- Упростить код при работе с вложенными итерациями
Простой пример
# ❌ Без 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 — это мощный инструмент для работы с генераторами, который делает код более читаемым, выразительным и немного быстрее при работе с вложенными итерациями.