PHP, C #, Python и, вероятно, некоторые другие языки имеют yield
ключевое слово, которое используется для создания функций генератора.
В PHP: http://php.net/manual/en/language.generators.syntax.php
В Python: https://www.pythoncentral.io/python-generators-and-yield-keyword/
В C #: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/yield
Я обеспокоен тем, что как языковая функция / средство yield
нарушает некоторые соглашения. Одним из них является то, что я бы назвал "уверенность". Это метод, который возвращает разные результаты каждый раз, когда вы вызываете его. Вы можете вызывать обычную функцию, не являющуюся генератором, и если ей будет дан тот же вход, он вернет тот же результат. С помощью yield он возвращает различный вывод в зависимости от своего внутреннего состояния. Таким образом, если вы случайно вызываете генерирующую функцию, не зная ее предыдущего состояния, вы не можете ожидать, что она вернет определенный результат.
Как такая функция вписывается в языковую парадигму? Это на самом деле нарушает какие-либо соглашения? Это хорошая идея, чтобы иметь и использовать эту функцию? (привести пример того, что хорошо, а что плохо, goto
когда-то было характерной чертой многих языков и до сих пор есть, но это считается вредным и как таковое было искоренено в некоторых языках, таких как Java). Должны ли компиляторы / интерпретаторы языка программирования нарушать какие-либо соглашения для реализации такой функции, например, должен ли язык реализовывать многопоточность, чтобы эта функция работала, или это можно сделать без технологии потоков?
yield
это по сути государственный двигатель. Это не означает, что нужно возвращать один и тот же результат каждый раз. Что он будет делать с абсолютной уверенностью, так это возвращать следующий элемент в перечисляемом каждый раз, когда он вызывается. Темы не требуются; вам нужно закрытие (более или менее), чтобы поддерживать текущее состояние.yield
ключевого слова, как в Python. У него есть статический методstd::this_thread::yield()
, но это не ключевое слово. Таким образом, к немуthis_thread
будет добавлен практически любой вызов, что делает очевидным, что это библиотечная функция только для получения потоков, а не языковая функция для получения потока управления в целом.Ответы:
Сначала предостережения - C # - это язык, который я знаю лучше всего, и хотя он имеет язык,
yield
который очень похож на другие языки »yield
, могут быть тонкие различия, о которых я не знаю.Туфта. Вы действительно ожидаете
Random.Next
илиConsole.ReadLine
возвращаете один и тот же результат каждый раз, когда звоните им? Как насчет отдыха звонки? Аутентификация? Получить товар из коллекции? Есть все виды (хорошие, полезные) функции, которые нечисты.Да,
yield
очень плохо играетtry/catch/finally
и не допускается ( https://blogs.msdn.microsoft.com/ericlippert/2009/07/16/iterator-blocks-part-three-why-no-yield-in-finally/ для больше информации).Это, безусловно, хорошая идея, чтобы иметь эту функцию. Такие вещи, как LINQ в C #, действительно хороши - ленивая оценка коллекций дает большое преимущество в производительности и
yield
позволяет делать такие вещи в части кода с частью ошибок, которые мог бы сделать ручной итератор.Тем не менее, не существует тонны использования для
yield
внешней обработки коллекции стилей LINQ. Я использовал его для обработки валидации, генерации расписания, рандомизации и некоторых других вещей, но я ожидаю, что большинство разработчиков никогда не использовали его (и не злоупотребляли им).Не совсем. Компилятор генерирует итератор конечного автомата, который отслеживает, где он остановился, чтобы он мог начать там снова при следующем вызове. Процесс генерации кода делает что-то похожее на стиль продолжения продолжения, когда код после
yield
него перетаскивается в свой собственный блок (и, если он имеет какие-либоyield
s, другой подблок и т. Д.). Это хорошо известный подход, который чаще используется в функциональном программировании, а также обнаруживается в асинхронной / ожидающей компиляции C #.Нет необходимости в многопоточности, но он требует другого подхода к генерации кода в большинстве компиляторов и имеет некоторый конфликт с функциями других языков.
В общем и целом,
yield
это относительно слабая функция, которая действительно помогает с определенным набором проблем.источник
yield
ключевое слово похоже на сопрограммы, да, или что-то другое? Если так, то я хотел бы иметь один в C! Я могу вспомнить хотя бы несколько приличных участков кода, которые было бы намного проще написать с помощью такой языковой функции.async
/await
был добавлен к языку, кто-то реализовал его, используяyield
.Я хотел бы ответить на это с точки зрения Python с решительным да, это отличная идея .
Сначала я рассмотрю некоторые вопросы и предположения в вашем вопросе, а затем продемонстрирую распространенность генераторов и их необоснованную полезность в Python.
Это неверно Методы на объектах могут рассматриваться как сами функции с их собственным внутренним состоянием. В Python, поскольку все является объектом, вы можете фактически получить метод из объекта и передать этот метод (который привязан к объекту, из которого он получен, поэтому он запоминает свое состояние).
Другие примеры включают намеренно случайные функции, а также методы ввода, такие как сеть, файловая система и терминал.
Если языковая парадигма поддерживает такие вещи, как функции первого класса, а генераторы поддерживают другие языковые функции, такие как протокол Iterable, то они легко вписываются.
Нет. Так как он встроен в язык, соглашения построены вокруг и включают (или требуют!) Использование генераторов.
Как и с любой другой функцией, компилятор просто должен быть разработан для поддержки этой функции. В случае с Python функции уже являются объектами с состоянием (такими как аргументы по умолчанию и аннотации функций).
Интересный факт: стандартная реализация Python вообще не поддерживает многопоточность. Он имеет глобальную блокировку интерпретатора (GIL), поэтому на самом деле ничего не выполняется одновременно, если вы не запускаете второй процесс для запуска другого экземпляра Python.
примечание: примеры в Python 3
За пределами урожайности
Хотя
yield
ключевое слово можно использовать в любой функции, чтобы превратить его в генератор, это не единственный способ его создания. Python содержит выражения генератора, мощный способ четко выразить генератор в терминах другого итерируемого (включая другие генераторы)Как видите, синтаксис не только чист и читаем, но и встроенные функции, такие как
sum
генераторы принятия.С
Ознакомьтесь с предложением по улучшению Python для оператора With . Это сильно отличается от того, что вы можете ожидать от оператора With на других языках. С небольшой помощью стандартной библиотеки генераторы Python прекрасно работают как контекстные менеджеры для них.
Конечно, печатать вещи - это самая скучная вещь, которую вы можете здесь сделать, но она показывает видимые результаты. Более интересные варианты включают автоматическое управление ресурсами (открытие и закрытие файлов / потоков / сетевых подключений), блокировку параллелизма, временное завершение или замену функции и распаковку, а затем повторное сжатие данных. Если вызов функций подобен внедрению кода в ваш код, то операторы подобны переносу частей вашего кода в другой код. Как бы вы ни использовали его, это хороший пример легкой привязки к языковой структуре. Генераторы на основе доходности - не единственный способ создания контекстных менеджеров, но они, безусловно, удобны.
И частичное истощение
Для циклов в Python работать интересно. Они имеют следующий формат:
Во-первых, выражение, которое я вызвал
<iterable>
, вычисляется для получения итерируемого объекта. Во-вторых, итерируемый__iter__
вызвал это, и получающийся итератор сохранен за кулисами. Затем__next__
вызывается итератор, чтобы получить значение для привязки к имени, которое вы вводите<name>
. Этот шаг повторяется до тех пор, пока вызов__next__
кидает aStopIteration
. Исключение поглощается циклом for, и выполнение продолжается оттуда.Возвращаясь к генераторам: когда вы вызываете
__iter__
генератор, он просто возвращает себя.Это означает, что вы можете отделить итерации по чему-то от того, что вы хотите с ним сделать, и изменить это поведение на середине пути. Ниже обратите внимание, как один и тот же генератор используется в двух циклах, а во втором он начинает выполняться с того места, где он остановился с первого.
Ленивая оценка
Одна из недостатков генераторов по сравнению со списками - единственное, что вы можете получить в генераторе, это следующее, что из этого получается. Вы не можете вернуться к предыдущему результату или перейти к следующему без промежуточных результатов. Положительным моментом является то, что генератор может почти не занимать память по сравнению с аналогичным списком.
Генераторы также могут быть лениво прикованы цепью.
Первая, вторая и третья строки просто определяют каждый генератор, но не выполняют никакой реальной работы. Когда вызывается последняя строка, sum запрашивает у numericcolumn значение, numericcolumn требуется значение из lastcolumn, lastcolumn запрашивает значение из файла журнала, который затем фактически читает строку из файла. Этот стек раскручивается, пока сумма не получит свое первое целое число. Затем процесс повторяется для второй строки. На данный момент сумма имеет два целых числа и складывает их вместе. Обратите внимание, что третья строка еще не была прочитана из файла. Затем Sum продолжает запрашивать значения у числового столбца (полностью игнорируя остальную часть цепочки) и добавлять их, пока числовой столбец не будет исчерпан.
Здесь действительно интересно то, что строки читаются, потребляются и отбрасываются по отдельности. Ни в коем случае не весь файл в памяти все сразу. Что произойдет, если этот файл журнала, скажем, терабайт? Это просто работает, потому что он читает только одну строку за раз.
Вывод
Это не полный обзор всех применений генераторов в Python. Примечательно, что я пропустил бесконечные генераторы, конечные автоматы, возвращение значений и их связь с сопрограммами.
Я верю, что этого достаточно, чтобы продемонстрировать, что у вас могут быть генераторы как чисто интегрированная, полезная языковая функция.
источник
Если вы привыкли к классическим языкам ООП, к генераторам и
yield
может показаться раздражающим, потому что изменяемое состояние фиксируется на уровне функций, а не на уровне объектов.Вопрос «определенности» - это красная сельдь. Обычно это называется ссылочной прозрачностью и в основном означает, что функция всегда возвращает один и тот же результат для одних и тех же аргументов. Как только у вас появляется изменяемое состояние, вы теряете ссылочную прозрачность. В ООП объекты часто имеют изменяемое состояние, что означает, что результат вызова метода зависит не только от аргументов, но и от внутреннего состояния объекта.
Вопрос в том, где захватить изменчивое состояние. В классическом ООП изменяемое состояние существует на уровне объекта. Но если языковая поддержка закрывается, у вас может быть изменяемое состояние на уровне функций. Например в JavaScript:
Короче говоря,
yield
это естественно в языке, который поддерживает замыкания, но неуместен в языке, подобном более старой версии Java, где изменяемое состояние существует только на уровне объекта.источник
На мой взгляд, это не очень хорошая особенность. Это плохая особенность, в первую очередь потому, что ее нужно учить очень осторожно, а все учат этому неправильно. Люди используют слово «генератор», разделяющее функцию генератора и объект генератора. Вопрос в следующем: кто или что делает на самом деле?
Это не просто мое мнение. Даже Гвидо в бюллетене PEP, в котором он руководствуется этим, признает, что функция генератора - это не генератор, а «фабрика генераторов».
Это важно, не правда ли? Но, прочитав 99% документации, у вас сложится впечатление, что функция генератора является фактическим генератором, и они склонны игнорировать тот факт, что вам также нужен объект генератора.
Гвидо подумал о замене «def» на «gen» для этих функций и сказал «Нет». Но я бы сказал, что этого было бы недостаточно. Это действительно должно быть:
источник