Что делает конструкция yield from в Python и в чем ее польза

Ответ

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

Проблема, которую решает yield from: Без этой конструкции для итерации по вложенному генератору пришлось бы писать громоздкий цикл.

# Без yield from
def chain_iterables(iter1, iter2):
    for value in iter1:
        yield value
    for value in iter2:
        yield value

print(list(chain_iterables(range(3), "ABC")))
# Вывод: [0, 1, 2, 'A', 'B', 'C']

Решение с yield from: Код становится значительно чище и декларативнее.

# С yield from
def chain_with_yield_from(iter1, iter2):
    yield from iter1
    yield from iter2

print(list(chain_with_yield_from(range(3), "ABC")))
# Вывод: [0, 1, 2, 'A', 'B', 'C']

Основные преимущества:

  • Краткость и читаемость: Заменяет вложенный цикл for ... yield, делая намерение очевидным.
  • Производительность: Делегирование происходит на уровне интерпретатора, что эффективнее ручного перебора в Python-коде.
  • Двусторонняя связь: yield from не только получает значения, но и пробрасывает вызовы .send(), .throw() и .close() напрямую в подгенератор. Это критически важно для продвинутых сценариев использования генераторов, включая корутины.
  • Основа для async/await: Механизм await в асинхронных функциях построен на тех же принципах делегирования, что и yield from.

Ответ 18+ 🔞

Вот, представляешь, сидишь ты, пишешь генератор, и тут тебе надо в него другой генератор впихнуть. А без yield from это выглядит как цирк с конями, ей-богу. Надо в цикле по нему бегать и каждый чих наружу выплёвывать. Полная жесть, блядь.

# Вот этот уёбищный способ, который все писали до него
def chain_iterables(iter1, iter2):
    for value in iter1:
        yield value
    for value in iter2:
        yield value

Смотри, какая муторная хуйня. Два цикла, два yield... Читать противно, писать лень. И производительность, ясень пень, не ахти.

А теперь смотри, как это делают крутые пацаны с yield from:

# А вот так — красиво, коротко, и в кассу
def chain_with_yield_from(iter1, iter2):
    yield from iter1
    yield from iter2

Во, бля! Одна строчка вместо цикла. Сразу видно, что тут происходит: "эй, iter1, давай сюда всё, что у тебя есть, я просто передам дальше". Красота, ёпта!

Но это, понимаешь, только верхушка айсберга, как говорится. Главная фишка yield from не в том, что он короткий. А в том, что он создаёт прямую, ебучую, трубу между внешним кодом и тем генератором, который внутри.

Что это значит на практике? А то, что если ты шлёшь в главный генератор значение через .send(value), то это значение не сдохнет на полпути, а доедет прямиком во внутренний, вложенный генератор! И если там исключение кинуть через .throw(), то оно тоже прямиком туда улетит. Без этой штуки пришлось бы руками всю эту логику проксирования писать, мозг сломаешь.

И самое, блядь, главное — это же основа для всей этой асинхронной хуйни с async/await! await — это по сути тот же самый yield from, только в костюме и при галстуке. Всё держится на этом механизме делегирования. Так что не просто синтаксический сахар, а фундаментальная, ёбта, вещь в языке.

Короче, yield from — это как взять и сказать: "Слушай, чувак, я тут постою, а ты управляй этим подгенератором напрямую, мне всё равно". И все довольны: и код чистый, и работает быстро, и возможности — овердохуища.