Мне трудно оборачивать свой мозг вокруг PEP 380 .
- В каких ситуациях полезно использовать «yield from»?
- Какой классический вариант использования?
- Почему это по сравнению с микропотоками?
[ Обновить ]
Теперь я понимаю причину моих трудностей. Я использовал генераторы, но никогда не использовал сопрограммы (представленный PEP-342 ). Несмотря на некоторое сходство, генераторы и сопрограммы - это две разные концепции. Понимание сопрограмм (не только генераторов) является ключом к пониманию нового синтаксиса.
ИМХО сопрограммы - самая неясная особенность Python , большинство книг делают ее бесполезной и неинтересной.
Спасибо за отличные ответы, но особую благодарность agf и его комментариям, связанным с презентациями Дэвида Бизли . Дэвид качается.
Ответы:
Давайте сначала уберем одну вещь. Объяснение,
yield from g
которое эквивалентноfor v in g: yield v
, даже не начинает отдавать должное тому, о чемyield from
идет речь. Потому что, давайте посмотрим правде в глаза: если всеyield from
это расширяетfor
цикл, то это не гарантирует добавленияyield from
в язык и препятствует реализации целого ряда новых функций в Python 2.x.Что он
yield from
делает, это устанавливает прозрачное двунаправленное соединение между вызывающим абонентом и вспомогательным генератором :Соединение является «прозрачным» в том смысле, что оно будет также правильно распространять все, а не только генерируемые элементы (например, распространяются исключения).
Соединение является «двунаправленным» в том смысле, что данные могут передаваться как от генератора, так и от него.
( Если бы мы говорили о TCP, это
yield from g
может означать «сейчас временно отключите сокет моего клиента и снова подключите его к этому другому сокету сервера». )Кстати, если вы не уверены, что вообще означает отправка данных в генератор , вам нужно сначала все отбросить и прочитать о сопрограммах - они очень полезны (противопоставляют их подпрограммам ), но, к сожалению, менее известны в Python. Любопытный курс Дейва Бизли по сопрограммам - отличное начало. Прочитайте слайды 24-33 для быстрого ознакомления.
Чтение данных из генератора с использованием yield из
Вместо того, чтобы вручную перебирать
reader()
, мы можем просто сделатьyield from
это.Это работает, и мы исключили одну строку кода. И, вероятно, намерение немного яснее (или нет). Но жизнь ничего не меняет.
Отправка данных в генератор (сопрограмму) с использованием выхода из - Часть 1
Теперь давайте сделаем что-нибудь более интересное. Давайте создадим сопрограмму с именем,
writer
которая принимает отправленные на нее данные и записывает в сокет, fd и т. Д.Теперь возникает вопрос: как функция-оболочка должна обрабатывать отправку данных в устройство записи, чтобы любые данные, отправляемые в оболочку, прозрачно отправлялись в
writer()
?Оболочка должна принимать данные, которые ей отправляются (очевидно), а также обрабатывать,
StopIteration
когда цикл for исчерпан. Очевидно, просто делатьfor x in coro: yield x
не буду. Вот версия, которая работает.Или мы могли бы сделать это.
Это экономит 6 строк кода, делает его намного более читабельным, и это просто работает. Магия!
Отправка данных в генератор возвращает из - Часть 2. Обработка исключений
Давайте сделаем это более сложным. Что если нашему писателю нужно обработать исключения? Допустим,
writer
дескрипторы a,SpamException
и он печатает,***
если он встречает один.Что если мы не изменимся
writer_wrapper
? Это работает? Давай попробуемХм, это не работает, потому что
x = (yield)
просто вызывает исключение, и все останавливается. Давайте сделаем это, но вручную обработаем исключения и отправим их или выбросим в суб-генератор (writer
)Это работает.
Но это так!
В
yield from
прозрачно ручки посылая значения или бросать значения в суб-генератора.Это все еще не покрывает все угловые случаи все же. Что произойдет, если внешний генератор закрыт? Как насчет случая, когда суб-генератор возвращает значение (да, в Python 3.3+ генераторы могут возвращать значения), как должно передаваться возвращаемое значение? Это
yield from
прозрачно обрабатывает все угловые случаи, действительно впечатляет .yield from
просто магически работает и обрабатывает все эти случаи.Я лично считаю
yield from
, что выбор плохого ключевого слова плох, потому что он не делает очевидной двустороннюю природу. Были предложены другие ключевые слова (например,delegate
но они были отклонены, потому что добавить новое ключевое слово в язык намного сложнее, чем объединить существующие).Таким образом, лучше всего думать о том,
yield from
какtransparent two way channel
между вызывающим и вспомогательным генератором.Ссылки:
источник
except StopIteration: pass
INSIDEwhile True:
цикла не является точным представлениемyield from coro
- которое не является бесконечным циклом и после того, какcoro
оно исчерпано (то есть вызывает StopIteration),writer_wrapper
выполнит следующий оператор. После последнего заявления он сам автоматически поднимется,StopIteration
как любой измотанный генератор ...writer
содержится , то после печати он ТАКЖЕ автоматически поднимет, и это будет автоматически обработано, а затем автоматически поднимет его собственный, и, поскольку он не находится внутри блока, он будет фактически поднят в этот момент ( то есть traceback будет сообщать только строку , а не что-либо изнутри генератора)for _ in range(4)
while True
>> 3
StopIteration
yield from
writer_wrapper
StopIteration
wrap.send(i)
try
wrap.send(i)
В каждой ситуации, когда у вас есть такой цикл:
Как описывает PEP, это довольно наивная попытка использования субгенератора, в нем отсутствуют некоторые аспекты, особенно правильная обработка механизмов
.throw()
/.send()
/.close()
, введенных в PEP 342 . Чтобы сделать это правильно, необходим довольно сложный код.Учтите, что вы хотите извлечь информацию из рекурсивной структуры данных. Допустим, мы хотим получить все листовые узлы в дереве:
Еще более важным является тот факт, что до этого
yield from
не было простого метода рефакторинга кода генератора. Предположим, у вас есть (бессмысленный) генератор, подобный этому:Теперь вы решили разделить эти циклы на отдельные генераторы. Без
yield from
этого это ужасно, вплоть до того момента, когда вы дважды подумаете, действительно ли вы хотите это сделать. Сyield from
, на самом деле приятно смотреть на:Я думаю, что этот раздел в PEP говорит о том, что каждый генератор имеет свой собственный изолированный контекст выполнения. Вместе с тем, что выполнение переключается между генератором-итератором и вызывающей стороной с использованием
yield
и__next__()
, соответственно, это похоже на потоки, где операционная система время от времени переключает исполняющий поток вместе с контекстом выполнения (стек, регистры, ...).Эффект этого также сопоставим: и генератор-итератор, и вызывающая программа одновременно переходят в состояние выполнения, их выполнения чередуются. Например, если генератор выполняет какие-то вычисления, а вызывающий абонент печатает результаты, вы увидите результаты, как только они станут доступны. Это форма параллелизма.
Впрочем, эта аналогия не является чем-то конкретным
yield from
- это скорее общее свойство генераторов в Python.источник
get_list_values_as_xxx
это простые генераторы с одной строкойfor x in input_param: yield int(x)
и двумя другими соответственно сstr
иfloat
Где бы вы вызываете генератор изнутри генератора вам нужно «прокачать» повторно
yield
значения:for v in inner_generator: yield v
. Как указывает PEP, в этом есть тонкие сложности, которые большинство людей игнорируют. Нелокальное управление потоком подобноthrow()
одному из примеров, приведенных в PEP. Новый синтаксисyield from inner_generator
используется везде, где вы бы написали явныйfor
цикл раньше. Это не просто синтаксический сахар: он обрабатывает все угловые случаи, которые игнорируютсяfor
циклом. Быть «сладким» поощряет людей использовать его и, таким образом, получать правильное поведение.Это сообщение в ветке обсуждения говорит об этих сложностях:
Я не могу говорить о сравнении с микропотоками, кроме как наблюдать, что генераторы - это тип паралеллизма. Вы можете считать приостановленный генератор потоком, который отправляет значения через
yield
поток потребителя. Реальная реализация может не иметь ничего общего с этим (и фактическая реализация, очевидно, представляет большой интерес для разработчиков Python), но это не касается пользователей.Новый
yield from
синтаксис не добавляет никаких дополнительных возможностей к языку с точки зрения потоков, он просто упрощает правильное использование существующих функций. Точнее, начинающему потребителю сложного внутреннего генератора, написанного экспертом, легче проходить через этот генератор, не нарушая ни одной из его сложных функций.источник
Короткий пример поможет вам понять один из вариантов
yield from
использования: получить значение из другого генератораисточник
print(*flatten([1, [2], [3, [4]]]))
yield from
в основном цепочки итераторов эффективным способом:Как вы можете видеть, он удаляет один чистый цикл Python. Это почти все, что он делает, но связывание итераторов - довольно распространенный шаблон в Python.
Потоки - это в основном функция, которая позволяет вам выпрыгивать из функций в совершенно случайных точках и возвращаться в состояние другой функции. Супервизор потоков делает это очень часто, поэтому программа запускает все эти функции одновременно. Проблема состоит в том, что точки случайны, поэтому вам нужно использовать блокировку, чтобы запретить супервизору остановить функцию в проблемной точке.
Генераторы очень похожи на потоки в этом смысле: они позволяют вам указывать конкретные точки (где бы они ни были
yield
), куда вы можете прыгать и выходить. При использовании этого способа генераторы называются сопрограммами.Прочитайте этот превосходный учебник о сопрограмм в Python для более подробной информации
источник
throw()/send()/close()
это заyield
функции, которые,yield from
очевидно, должны реализовываться должным образом, поскольку это должно упростить код. Такие мелочи не имеют ничего общего с использованием.chain
функцию, потому что онаitertools.chain
уже существует. Использованиеyield from itertools.chain(*iters)
.В прикладном использовании для сопрограммы асинхронного ввода - вывода ,
yield from
имеет аналогичное поведение какawait
в сопрограммах функции . Оба из которых используются, чтобы приостановить выполнение сопрограммы.yield from
используется сопрограммой на основе генератора .await
используется дляasync def
сопрограмм. (начиная с Python 3.5+)Для Asyncio, если нет необходимости поддерживать более старую версию Python (т.е.> 3.5),
async def
/await
является рекомендуемым синтаксисом для определения сопрограммы. Таким образомyield from
, больше не требуется сопрограмма.Но в целом за пределами asyncio
yield from <sub-generator>
все еще используется итерация вспомогательного генератора, как упоминалось в предыдущем ответе.источник
Этот код определяет функцию,
fixed_sum_digits
возвращающую генератор, перечисляющий все шесть цифр, так что сумма цифр равна 20.Попробуйте написать это без
yield from
. Если вы найдете эффективный способ сделать это, дайте мне знать.Я думаю, что для подобных случаев: посещение деревьев
yield from
делает код проще и чище.источник
Проще говоря,
yield from
предоставляет хвостовую рекурсию для функций итератора.источник