Чтобы понять, что yield
значит, вы должны понять, что такое генераторы . И прежде чем вы сможете понять генераторы, вы должны понять итерируемые .
итерируемыми
Когда вы создаете список, вы можете читать его элементы по одному. Чтение его элементов по одному называется итерацией:
>>> mylist = [1, 2, 3]
>>> for i in mylist:
... print(i)
1
2
3
mylist
является итерацию . Когда вы используете понимание списка, вы создаете список, и поэтому повторяемый:
>>> mylist = [x*x for x in range(3)]
>>> for i in mylist:
... print(i)
0
1
4
Все, что вы можете использовать " for... in...
", является итеративным; lists
, strings
файлы ...
Эти итерации удобны, потому что вы можете читать их сколько угодно, но вы храните все значения в памяти, и это не всегда то, что вы хотите, когда у вас много значений.
Генераторы
Генераторы - это итераторы, вид итерации, который вы можете повторять только один раз . Генераторы не хранят все значения в памяти, они генерируют значения на лету :
>>> mygenerator = (x*x for x in range(3))
>>> for i in mygenerator:
... print(i)
0
1
4
Это то же самое, за исключением того, что вы использовали ()
вместо []
. НО, вы не можете выполнить for i in mygenerator
второй раз, так как генераторы могут использоваться только один раз: они вычисляют 0, затем забывают об этом и вычисляют 1, и заканчивают вычислять 4, один за другим.
Уступать
yield
это ключевое слово, которое используется как return
, за исключением того, что функция вернет генератор.
>>> def createGenerator():
... mylist = range(3)
... for i in mylist:
... yield i*i
...
>>> mygenerator = createGenerator() # create a generator
>>> print(mygenerator) # mygenerator is an object!
<generator object createGenerator at 0xb7555c34>
>>> for i in mygenerator:
... print(i)
0
1
4
Здесь это бесполезный пример, но он удобен, когда вы знаете, что ваша функция вернет огромный набор значений, которые вам нужно будет прочитать только один раз.
Чтобы освоить yield
, вы должны понимать, что при вызове функции код, написанный в теле функции, не запускается. Функция возвращает только объект генератора, это немного сложно :-)
Затем ваш код будет продолжаться с того места, где он остановился, каждый раз, когда for
используется генератор.
Теперь самая сложная часть:
При первом for
вызове объекта генератора, созданного из вашей функции, он будет запускать код в вашей функции с самого начала, пока не получит совпадение yield
, а затем вернет первое значение цикла. Затем каждый последующий вызов будет запускать очередную итерацию цикла, который вы написали в функции, и возвращать следующее значение. Это будет продолжаться до тех пор, пока генератор не будет считаться пустым, что происходит, когда функция запускается без нажатия кнопки yield
. Это может быть из-за того, что цикл закончился, или из-за того, что вы больше не удовлетворяете "if/else"
.
Ваш код объяснил
Генератор:
# Here you create the method of the node object that will return the generator
def _get_child_candidates(self, distance, min_dist, max_dist):
# Here is the code that will be called each time you use the generator object:
# If there is still a child of the node object on its left
# AND if the distance is ok, return the next child
if self._leftchild and distance - max_dist < self._median:
yield self._leftchild
# If there is still a child of the node object on its right
# AND if the distance is ok, return the next child
if self._rightchild and distance + max_dist >= self._median:
yield self._rightchild
# If the function arrives here, the generator will be considered empty
# there is no more than two values: the left and the right children
Абонент:
# Create an empty list and a list with the current object reference
result, candidates = list(), [self]
# Loop on candidates (they contain only one element at the beginning)
while candidates:
# Get the last candidate and remove it from the list
node = candidates.pop()
# Get the distance between obj and the candidate
distance = node._get_dist(obj)
# If distance is ok, then you can fill the result
if distance <= max_dist and distance >= min_dist:
result.extend(node._values)
# Add the children of the candidate in the candidate's list
# so the loop will keep running until it will have looked
# at all the children of the children of the children, etc. of the candidate
candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))
return result
Этот код содержит несколько умных частей:
Цикл повторяется в списке, но список расширяется во время итерации цикла :-) Это краткий способ пройти через все эти вложенные данные, даже если это немного опасно, так как вы можете получить бесконечный цикл. В этом случае candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))
исчерпайте все значения генератора, но while
продолжайте создавать новые объекты генератора, которые будут производить значения, отличные от предыдущих, так как он не применяется к одному узлу.
extend()
Метод является методом объекта списка , который ожидает , что итератор и добавляет его значение в список.
Обычно мы передаем ему список:
>>> a = [1, 2]
>>> b = [3, 4]
>>> a.extend(b)
>>> print(a)
[1, 2, 3, 4]
Но в вашем коде он получает генератор, что хорошо, потому что:
- Вам не нужно читать значения дважды.
- У вас может быть много детей, и вы не хотите, чтобы они все хранились в памяти.
И это работает, потому что Python не заботится, является ли аргумент метода списком или нет. Python ожидает итерации, поэтому он будет работать со строками, списками, кортежами и генераторами! Это называется утка и является одной из причин, почему Python такой крутой. Но это другая история, для другого вопроса ...
Вы можете остановиться здесь или прочитать немного, чтобы увидеть расширенное использование генератора:
Контроль истощения генератора
>>> class Bank(): # Let's create a bank, building ATMs
... crisis = False
... def create_atm(self):
... while not self.crisis:
... yield "$100"
>>> hsbc = Bank() # When everything's ok the ATM gives you as much as you want
>>> corner_street_atm = hsbc.create_atm()
>>> print(corner_street_atm.next())
$100
>>> print(corner_street_atm.next())
$100
>>> print([corner_street_atm.next() for cash in range(5)])
['$100', '$100', '$100', '$100', '$100']
>>> hsbc.crisis = True # Crisis is coming, no more money!
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> wall_street_atm = hsbc.create_atm() # It's even true for new ATMs
>>> print(wall_street_atm.next())
<type 'exceptions.StopIteration'>
>>> hsbc.crisis = False # The trouble is, even post-crisis the ATM remains empty
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> brand_new_atm = hsbc.create_atm() # Build a new one to get back in business
>>> for cash in brand_new_atm:
... print cash
$100
$100
$100
$100
$100
$100
$100
$100
$100
...
Примечание: для Python 3 используйте print(corner_street_atm.__next__())
илиprint(next(corner_street_atm))
Это может быть полезно для различных вещей, таких как управление доступом к ресурсу.
Itertools, твой лучший друг
Модуль itertools содержит специальные функции для управления итерациями. Вы когда-нибудь хотели дублировать генератор? Цепочка двух генераторов? Группировать значения во вложенном списке с одной линией? Map / Zip
без создания другого списка?
Тогда просто import itertools
.
Пример? Давайте посмотрим возможные порядки заезда на скачки:
>>> horses = [1, 2, 3, 4]
>>> races = itertools.permutations(horses)
>>> print(races)
<itertools.permutations object at 0xb754f1dc>
>>> print(list(itertools.permutations(horses)))
[(1, 2, 3, 4),
(1, 2, 4, 3),
(1, 3, 2, 4),
(1, 3, 4, 2),
(1, 4, 2, 3),
(1, 4, 3, 2),
(2, 1, 3, 4),
(2, 1, 4, 3),
(2, 3, 1, 4),
(2, 3, 4, 1),
(2, 4, 1, 3),
(2, 4, 3, 1),
(3, 1, 2, 4),
(3, 1, 4, 2),
(3, 2, 1, 4),
(3, 2, 4, 1),
(3, 4, 1, 2),
(3, 4, 2, 1),
(4, 1, 2, 3),
(4, 1, 3, 2),
(4, 2, 1, 3),
(4, 2, 3, 1),
(4, 3, 1, 2),
(4, 3, 2, 1)]
Понимание внутренних механизмов итерации
Итерация - это процесс, подразумевающий итераторы (реализующие __iter__()
метод) и итераторы (реализующие __next__()
метод). Итерации - это любые объекты, от которых вы можете получить итератор. Итераторы - это объекты, которые позволяют повторять итерации.
В этой статье рассказывается больше о том, как for
работают циклы .
yield
этот ответ не так волшебен, как предполагает. Когда вы вызываете функцию, которая содержитyield
оператор где-либо, вы получаете объект генератора, но код не запускается. Затем каждый раз, когда вы извлекаете объект из генератора, Python выполняет код в функции, пока не доходит доyield
оператора, затем приостанавливает и доставляет объект. Когда вы извлекаете другой объект, Python возобновляется сразу послеyield
и продолжается до тех пор, пока не достигнет другогоyield
(часто того же самого, но одной итерации позже). Это продолжается до тех пор, пока функция не завершится, и в этот момент генератор считается исчерпанным.()
вместо него[]
, а именно, что()
есть (может быть путаница с кортежем).return
оператор. (return
разрешено в функции, содержащейyield
, при условии, что она не указывает возвращаемое значение.)Ярлык для понимания
yield
Когда вы увидите функцию с
yield
операторами, примените этот простой трюк, чтобы понять, что произойдет:result = []
в начале функции.yield expr
наresult.append(expr)
.return result
в нижней части функции.yield
заявлений! Прочитайте и выясните код.Этот прием может дать вам представление о логике функции, но то, что происходит на самом деле,
yield
значительно отличается от того, что происходит в подходе, основанном на списке. Во многих случаях подход с выходом будет намного более эффективным и быстрее. В других случаях этот трюк застрянет в бесконечном цикле, даже если оригинальная функция работает просто отлично. Читайте дальше, чтобы узнать больше...Не путайте ваши итераторы, итераторы и генераторы
Во-первых, протокол итератора - когда вы пишете
Python выполняет следующие два шага:
Получает итератор для
mylist
:Call
iter(mylist)
-> возвращает объект сnext()
методом (или__next__()
в Python 3).[Это шаг, о котором большинство людей забывают рассказать вам]
Использует итератор для зацикливания элементов:
Продолжайте вызывать
next()
метод на итераторе, возвращенном с шага 1. Возвращаемое значение изnext()
присваивается,x
и тело цикла выполняется. Если исключениеStopIteration
вызывается изнутриnext()
, это означает, что в итераторе больше нет значений и цикл завершается.Правда в том, что Python выполняет два вышеупомянутых шага в любое время, когда он хочет перебрать содержимое объекта - так что это может быть цикл for, но он также может быть похож на код
otherlist.extend(mylist)
(гдеotherlist
список Python).Вот
mylist
это итерация , поскольку он реализует протокол итератора. В пользовательском классе вы можете реализовать__iter__()
метод, чтобы сделать экземпляры вашего класса итеративными. Этот метод должен возвращать итератор . Итератор - это объект сnext()
методом. Можно реализовать как__iter__()
иnext()
на одном и том же классе, так и иметь__iter__()
возвратself
. Это будет работать для простых случаев, но не когда вы хотите, чтобы два итератора циклически обрабатывали один и тот же объект одновременно.Так что это протокол итератора, многие объекты реализуют этот протокол:
__iter__()
.Обратите внимание, что
for
цикл не знает, с каким объектом он имеет дело - он просто следует протоколу итератора и рад получить элемент за элементом при вызовеnext()
. Встроенные списки возвращают свои элементы один за другим, словари возвращают ключи один за другим, файлы возвращают строки одну за другой и т. Д. И генераторы возвращают ... что ж, вот чтоyield
приходит:Вместо
yield
операторов, если у вас было триreturn
оператора,f123()
выполнялся только первый, и функция завершалась. Ноf123()
это не обычная функция. Когдаf123()
вызывается, он не возвращает никаких значений в операторах yield! Возвращает объект генератора. Кроме того, функция на самом деле не выходит - она переходит в состояние ожидания. Когдаfor
цикл пытается перебрать объект-генератор, функция возвращается из приостановленного состояния на самой следующей строке после того, какyield
она была ранее возвращена, выполняет следующую строку кода, в данном случаеyield
инструкцию, и возвращает ее как следующую вещь. Это происходит до тех пор, пока функция не выйдет, и в этот момент генератор сработаетStopIteration
и цикл завершится.Таким образом, объект генератора в некотором роде похож на адаптер - с одной стороны он демонстрирует протокол итератора, предоставляя
__iter__()
иnext()
методы для поддержанияfor
цикла счастливыми. На другом конце, однако, он запускает функцию, достаточную для получения следующего значения, и переводит ее обратно в режим ожидания.Зачем использовать генераторы?
Обычно вы можете написать код, который не использует генераторы, но реализует ту же логику. Одним из вариантов является использование временного списка «трюк», который я упоминал ранее. Это не будет работать во всех случаях, например, если у вас бесконечные циклы, или это может привести к неэффективному использованию памяти, когда у вас действительно длинный список. Другой подход заключается в реализации нового итерируемого класса SomethingIter, который сохраняет состояние в элементах экземпляра и выполняет следующий логический шаг в своем
next()
(или__next__()
в Python 3) методе. В зависимости от логики, код внутриnext()
метода может выглядеть очень сложным и быть подверженным ошибкам. Здесь генераторы обеспечивают чистое и простое решение.источник
send
создать генератор, который является огромной частью идеи генераторов?otherlist.extend(mylist)
" -> Это неверно.extend()
изменяет список на месте и не возвращает итерацию. Попытка зацикливатьсяotherlist.extend(mylist)
не удастся,TypeError
так какextend()
неявно возвращаетNone
, и вы не можете зациклитьNone
.mylist
(а не при включенииotherlist
)otherlist.extend(mylist)
.Думайте об этом так:
Итератор - это просто причудливый термин для объекта, у которого есть
next()
метод. Таким образом, функция yield-ed в итоге выглядит примерно так:Оригинальная версия:
Это в основном то, что интерпретатор Python делает с приведенным выше кодом:
Чтобы лучше понять, что происходит за кулисами,
for
цикл можно переписать так:Это имеет больше смысла или просто сбивает вас с толку? :)
Я хотел бы отметить , что это является упрощением в иллюстративных целях. :)
источник
__getitem__
может быть определено вместо__iter__
. Например:,class it: pass; it.__getitem__ = lambda self, i: i*10 if i < 10 else [][0]; for i in it(): print(i)
он будет печатать: 0, 10, 20, ..., 90iterator = some_function()
, переменнаяiterator
больше не имеет вызываемой функцииnext()
, а только__next__()
функцию. Думаю, я упомяну это.for
реализованная вами реализация цикла вызывает__iter__
методiterator
экземпляра экземпляраit
?yield
Ключевые слова сводятся к двум простым фактам:yield
ключевое слово где-либо внутри функции, эта функция больше не возвращается черезreturn
оператор. Вместо этого он немедленно возвращает ленивый объект «список ожидания», называемый генератором.list
или,set
или,range
или dict-view, со встроенным протоколом для посещения каждого элемента в определенном порядке .В двух словах: генератор - это ленивый, постепенно увеличивающийся список , а
yield
операторы позволяют использовать функцию обозначения для программирования значений списка, которые генератор должен постепенно выводить.пример
Давайте определим функцию,
makeRange
которая похожа на функцию Pythonrange
. ВызовmakeRange(n)
ВОЗВРАЩАЕТ ГЕНЕРАТОРА:Чтобы заставить генератор немедленно возвращать ожидающие значения, вы можете передать его
list()
(как и любой итеративный):Сравнение примера с «просто возвратом списка»
Приведенный выше пример можно рассматривать как простое создание списка, к которому вы добавляете и возвращаете:
Однако есть одно существенное отличие; смотрите последний раздел.
Как вы можете использовать генераторы
Итерируемое является последней частью понимания списка, и все генераторы являются итеративными, поэтому они часто используются так:
Чтобы лучше понять генераторы, вы можете поиграться с
itertools
модулем (используйте его,chain.from_iterable
а неchain
когда это требуется ). Например, вы можете даже использовать генераторы для реализации бесконечно длинных ленивых списков, таких какitertools.count()
. Вы можете реализовать свое собственноеdef enumerate(iterable): zip(count(), iterable)
или, альтернативно, сделать это сyield
ключевым словом в цикле while.Обратите внимание: генераторы могут использоваться для многих других целей, таких как реализация сопрограмм, недетерминированное программирование или другие элегантные вещи. Тем не менее, точка зрения «ленивых списков», которую я здесь представляю, является наиболее распространенной областью использования, которую вы найдете.
За кулисами
Вот как работает «Протокол итерации Python». То, что происходит, когда вы делаете
list(makeRange(5))
. Это то, что я описываю ранее как «ленивый, добавочный список».Встроенная функция
next()
просто вызывает.next()
функцию объектов , которая является частью «протокола итерации» и находится на всех итераторах. Вы можете вручную использоватьnext()
функцию (и другие части протокола итерации) для реализации модных вещей, обычно за счет читабельности, поэтому постарайтесь не делать этого ...мелочи
Обычно большинство людей не заботятся о следующих различиях и, вероятно, захотят перестать читать здесь.
В языке Python итеративный - это любой объект, который «понимает концепцию цикла for», подобного списку
[1,2,3]
, а итератор - это конкретный экземпляр запрошенного цикла for[1,2,3].__iter__()
. Генератор точно такой же , как и любой итератор, за то , как он был написан (с синтаксисом функции) , за исключением.Когда вы запрашиваете итератор из списка, он создает новый итератор. Однако, когда вы запрашиваете итератор у итератора (что вы редко делаете), он просто дает вам свою копию.
Таким образом, в маловероятном случае, если вы не в состоянии сделать что-то подобное ...
... потом помните, что генератор - это итератор ; то есть одноразовое использование. Если вы хотите использовать его повторно, вам следует позвонить
myRange(...)
снова. Если вам нужно использовать результат дважды, преобразуйте результат в список и сохраните его в переменнойx = list(myRange(5))
. Те, кому абсолютно необходимо клонировать генератор (например, кто выполняет ужасно хакерское метапрограммирование), могут использовать его,itertools.tee
если это абсолютно необходимо, поскольку предложение по стандарту Python PEP для копируемого итератора было отложено.источник
Схема ответа / Резюме
yield
вызовом возвращает Generator .yield from
.return
в генераторе.)Генераторы:
yield
допустимо только внутри определения функции, и включениеyield
в определение функции заставляет его возвращать генератор.Идея для генераторов исходит из других языков (см. Сноску 1) с различными реализациями. В генераторах Python выполнение кода заморожено в точке выхода. Когда вызывается генератор (методы обсуждаются ниже), выполнение возобновляется, а затем останавливается при следующем выходе.
yield
обеспечивает простой способ реализации протокола итератора , определяемого следующими двумя методами:__iter__
иnext
(Python 2) или__next__
(Python 3). Оба этих метода делают объект итератором, который вы можете проверить типом с помощьюIterator
Abstract Base Class изcollections
модуля.Тип генератора является подтипом итератора:
И при необходимости мы можем проверить тип так:
Особенность функции
Iterator
заключается в том, что после ее исчерпания вы не можете использовать ее повторно или сбросить:Вам придется сделать еще один, если вы хотите снова использовать его функциональность (см. Сноску 2):
Можно получать данные программно, например:
Вышеуказанный простой генератор также эквивалентен приведенному ниже - начиная с Python 3.3 (и недоступен в Python 2), вы можете использовать
yield from
:Тем не менее,
yield from
также допускается делегирование субгенераторам, что будет объяснено в следующем разделе о совместном делегировании с субпрограммами.Сопрограммы:
yield
формирует выражение, которое позволяет отправлять данные в генератор (см. сноску 3)Вот пример, обратите внимание на
received
переменную, которая будет указывать на данные, которые отправляются в генератор:Во-первых, мы должны поставить генератор в очередь с помощью встроенной функции
next
. Он будет вызывать соответствующий методnext
или__next__
, в зависимости от используемой версии Python:И теперь мы можем отправлять данные в генератор. ( Отправка
None
аналогична звонкуnext
.):Совместная делегация в суб-сопрограмме с
yield from
Теперь напомним, что
yield from
это доступно в Python 3. Это позволяет нам делегировать сопрограммы в подпрограмму:И теперь мы можем делегировать функциональность суб-генератору, и он может использоваться генератором, как указано выше:
Вы можете прочитать больше о точной семантике
yield from
в PEP 380.Другие методы: закрыть и бросить
close
Метод вызываетGeneratorExit
в точке выполнение функции было заморожено. Это также будет вызвано,__del__
так что вы можете поместить любой код очистки, где вы обрабатываетеGeneratorExit
:Вы также можете выдать исключение, которое может быть обработано в генераторе или передано обратно пользователю:
Вывод
Я считаю, что я охватил все аспекты следующего вопроса:
Оказывается,
yield
это многое делает. Я уверен, что мог бы добавить еще более подробные примеры к этому. Если вы хотите больше или имеете конструктивную критику, дайте мне знать, комментируя ниже.Приложение:
Критика топ / принятого ответа **
__iter__
метод, возвращающий итератор . Итератора предоставляет.next
(Python 2 или.__next__
метод (Python 3), который неявно вызываетсяfor
петлями до тех пор, пока не возникаетStopIteration
, и как только это произойдет, он будет продолжать это делать.yield
части..next
метод, когда вместо этого он должен использовать встроенную функциюnext
. Это был бы соответствующий уровень косвенности, потому что его код не работает в Python 3.yield
вообще делает.yield
предоставляют наряду с новой функциональностьюyield from
в Python 3. Верхний / принятый ответ является очень неполным ответом.Критика ответа на вопрос
yield
в генераторе выражения или понимания.В настоящее время грамматика допускает любое выражение в понимании списка.
Так как yield является выражением, некоторые считают его интересным для использования в пониманиях или выражениях-генераторах, несмотря на то, что он не привел ни одного особенно хорошего варианта использования.
Разработчики ядра CPython обсуждают отказ от его разрешения . Вот соответствующий пост из списка рассылки:
Кроме того, существует нерешенная проблема (10544), которая, кажется, указывает на то, что это никогда не будет хорошей идеей (PyPy, реализация Python, написанная на Python, уже вызывает предупреждения синтаксиса.)
В итоге, пока разработчики CPython не скажут нам иначе: не вставляйте
yield
в генератор выражения или понимание.return
Заявление в генератореВ Python 2 :
В
expression_list
основном это любое количество выражений, разделенных запятыми - по сути, в Python 2 вы можете остановить генераторreturn
, но не можете вернуть значение.В Python 3 :
Сноски
Языки CLU, Sather и Icon упоминались в предложении ввести концепцию генераторов в Python. Общая идея заключается в том, что функция может поддерживать внутреннее состояние и выдавать промежуточные точки данных по требованию пользователя. Это обещало быть превосходным по производительности по сравнению с другими подходами, включая потоки Python , которые даже недоступны в некоторых системах.
Это означает, например, что
xrange
объекты (range
в Python 3) неIterator
являются объектами, даже если они итеративны, потому что их можно использовать повторно. Как и списки, их__iter__
методы возвращают объекты итератора.yield
Первоначально был представлен как оператор, то есть он мог появляться только в начале строки в блоке кода. Теперьyield
создает выражение выхода. https://docs.python.org/2/reference/simple_stmts.html#grammar-token-yield_stmt Это изменение было предложено, чтобы позволить пользователю отправлять данные в генератор так же, как они могут быть получены. Чтобы отправить данные, нужно иметь возможность назначить их чему-либо, и для этого оператор просто не будет работать.источник
yield
это какreturn
- он возвращает все, что вы говорите (как генератор). Разница в том, что при следующем вызове генератора выполнение начинается с последнего вызоваyield
оператора. В отличие от return, кадр стека не очищается при возникновении выхода, однако управление передается обратно вызывающей стороне, поэтому его состояние возобновится при следующем вызове функции.В случае вашего кода функция
get_child_candidates
действует как итератор, поэтому при расширении списка она добавляет один элемент за один раз в новый список.list.extend
вызывает итератор, пока он не исчерпан. В случае с примером кода, который вы разместили, было бы намного проще просто вернуть кортеж и добавить его в список.источник
Есть еще одна вещь, которую стоит упомянуть: функция, которая возвращает результат, на самом деле не должна завершаться. Я написал такой код:
Тогда я могу использовать его в другом коде, как это:
Это действительно помогает упростить некоторые проблемы и облегчает работу с некоторыми вещами.
источник
Для тех, кто предпочитает минимальный рабочий пример, медитируйте на этом интерактивном сеансе Python:
источник
TL; DR
Вместо этого:
сделай это:
Всякий раз, когда вы обнаруживаете, что создаете список с нуля,
yield
вместо этого каждый кусок.Это был мой первый "ага" момент с доходностью.
yield
это сладкий способ сказатьТакое же поведение:
Разное поведение:
Доходность однопроходная : вы можете проходить только один раз. Когда у функции есть выход, мы называем ее функцией генератора . И итератор - это то, что он возвращает. Эти условия являются показательными. Мы теряем удобство контейнера, но получаем мощность ряда, который вычисляется по мере необходимости и произвольно долго.
Выход ленивый , он откладывает вычисления. Функция с выходом в нем фактически не выполняется вообще, когда вы ее вызываете. Он возвращает объект итератора, который запоминает, где он остановился. Каждый раз, когда вы вызываете
next()
итератор (это происходит в цикле for), выполнение в дюймах вперед до следующего выхода.return
вызывает StopItered и заканчивает серию (это естественный конец цикла for).Урожай универсален . Данные не должны храниться все вместе, они могут быть доступны по одному за раз. Это может быть бесконечно.
Если вам нужно несколько проходов, и серия не слишком длинная, просто позвоните
list()
по ней:Блестящий выбор слова,
yield
потому что применяются оба значения :... предоставьте следующие данные в серии.
... отказаться от выполнения процессора, пока итератор не продвинется.
источник
Выход дает вам генератор.
Как видите, в первом случае
foo
весь список хранится в памяти сразу. Это не имеет большого значения для списка из 5 элементов, но что, если вы хотите список из 5 миллионов? Мало того, что это огромный пожиратель памяти, он также требует много времени для создания во время вызова функции.Во втором случае
bar
просто дает вам генератор. Генератор является итеративным - это означает, что вы можете использовать его вfor
цикле и т. Д., Но к каждому значению можно получить доступ только один раз. Все значения также не сохраняются в памяти одновременно; объект генератора «запоминает», где он находился в цикле в последний раз, когда вы его вызывали - таким образом, если вы используете итеративный подсчет (скажем) до 50 миллиардов, вам не нужно считать до 50 миллиардов всех и запомните 50 миллиардов чиселОпять же, это довольно надуманный пример, вы, вероятно, использовали бы itertools, если бы вы действительно хотели сосчитать до 50 миллиардов. :)
Это самый простой вариант использования генераторов. Как вы сказали, его можно использовать для написания эффективных перестановок, используя yield для продвижения по стеку вызовов вместо использования некоторой переменной стека. Генераторы также могут быть использованы для специализированного обхода дерева и всего прочего.
источник
range
также возвращается генератор вместо списка, так что вы также увидите похожую идею, за исключением того, что__repr__
/__str__
переопределяется, чтобы показать более хороший результат, в этом случаеrange(1, 10, 2)
.Это возвращает генератор. Я не особенно знаком с Python, но я считаю, что это то же самое, что и блоки итераторов C #, если вы знакомы с ними.
Ключевая идея заключается в том, что компилятор / интерпретатор / что-либо делает какую-то хитрость, чтобы, что касается вызывающей стороны, они могли продолжать вызывать next (), и он продолжит возвращать значения - как если бы метод генератора был приостановлен . Теперь, очевидно, вы не можете «приостановить» метод, поэтому компилятор создает конечный автомат, чтобы вы могли запомнить, где вы находитесь в данный момент и как выглядят локальные переменные и т. Д. Это гораздо проще, чем написать итератор самостоятельно.
источник
Есть один тип ответа, который я пока не чувствую, среди множества отличных ответов, которые описывают, как использовать генераторы. Вот ответ теории языка программирования:
yield
Оператор в Python возвращает генератор. Генератор в Python - это функция, которая возвращает продолжения (и, в частности, тип сопрограммы, но продолжения представляют более общий механизм для понимания того, что происходит).Продолжения в теории языков программирования - гораздо более фундаментальный вид вычислений, но они не часто используются, потому что их чрезвычайно сложно рассуждать, а также очень сложно реализовать. Но идея о том, что такое продолжение, проста: это состояние вычислений, которое еще не закончено. В этом состоянии текущие значения переменных, операции, которые еще предстоит выполнить, и т. Д. Сохраняются. Затем в какой-то момент позже в программе может быть вызвано продолжение, так что переменные программы сбрасываются в это состояние и выполняются сохраненные операции.
Продолжения в этом более общем виде могут быть реализованы двумя способами. Таким
call/cc
образом, стек программы буквально сохраняется, а затем, когда вызывается продолжение, стек восстанавливается.В стиле передачи продолжения (CPS), продолжения - это просто обычные функции (только в языках, где функции первого класса), которыми программист явно управляет и передает их подпрограммам. В этом стиле состояние программы представлено замыканиями (и переменными, которые в них кодируются), а не переменными, которые находятся где-то в стеке. Функции, которые управляют потоком управления, принимают продолжение в качестве аргументов (в некоторых вариантах CPS функции могут принимать несколько продолжений) и управляют потоком управления, вызывая их, просто вызывая их и возвращая потом. Очень простой пример стиля передачи продолжения следующий:
В этом (очень упрощенном) примере программист сохраняет операцию фактической записи файла в продолжение (которое может быть очень сложной операцией с большим количеством деталей для записи), а затем передает это продолжение (т. Е. Как первый закрытие класса) другому оператору, который выполняет дополнительную обработку, а затем вызывает ее при необходимости. (Я часто использую этот шаблон проектирования в реальном программировании GUI, потому что он экономит мне строки кода или, что более важно, управляет потоком управления после запуска событий GUI.)
Остальная часть этого поста, без потери общности, будет концептуализировать продолжения как CPS, потому что это чертовски легко понять и прочитать.
Теперь поговорим о генераторах в Python. Генераторы - это определенный подтип продолжения. В то время как продолжения в целом могут сохранять состояние вычислений (т. Е. Стек вызовов программы), генераторы могут сохранять состояние итерации только через итератор . Хотя это определение слегка вводит в заблуждение для определенных случаев использования генераторов. Например:
Это явно разумная итерация, поведение которой четко определено - каждый раз, когда генератор повторяет ее, он возвращает 4 (и делает это всегда). Но это, вероятно, не тип прототипа итерируемого, который приходит на ум, когда мы думаем об итераторах (то есть
for x in collection: do_something(x)
). Этот пример иллюстрирует мощь генераторов: если что-то является итератором, генератор может сохранить состояние своей итерации.Повторим: продолжения могут сохранять состояние стека программы, а генераторы могут сохранять состояние итерации. Это означает, что продолжения более мощные, чем генераторы, но также и то, что генераторы намного, намного проще. Их легче реализовать для языкового дизайнера, и их легче использовать программисту (если у вас есть время для записи, попробуйте прочитать и понять эту страницу о продолжениях и вызвать / cc ).
Но вы можете легко реализовать (и концептуализировать) генераторы как простой, конкретный случай стиля передачи продолжения:
Всякий раз, когда
yield
вызывается, он говорит функции, чтобы возвратить продолжение. Когда функция вызывается снова, она начинается с того места, где она остановилась. Таким образом, в псевдопсевдокоде (т.е. не псевдокоде, а не коде) метод генератора вnext
основном выглядит следующим образом:где
yield
ключевое слово на самом деле является синтаксическим сахаром для реальной функции генератора, в основном что-то вроде:Помните, что это просто псевдокод, и фактическая реализация генераторов в Python более сложна. Но в качестве упражнения, чтобы понять, что происходит, попробуйте использовать стиль передачи продолжения для реализации объектов генератора без использования
yield
ключевого слова.источник
Вот пример на простом языке. Я приведу соответствие между человеческими концепциями высокого уровня и концепциями Python низкого уровня.
Я хочу работать с последовательностью чисел, но я не хочу беспокоить себя созданием этой последовательности, я хочу сосредоточиться только на операции, которую я хочу сделать. Итак, я делаю следующее:
Этот шаг соответствует
def
введению функции генератора, то есть функции, содержащей ayield
.Этот шаг соответствует вызову функции генератора, которая возвращает объект генератора. Обратите внимание, что вы еще не сказали мне никаких чисел; ты просто берешь свою бумагу и карандаш.
Этот шаг соответствует вызову
.next()
объекта генератора.Этот шаг соответствует объекту генератора, завершающему свою работу и вызывающему
StopIteration
исключение. Функция генератора не должна вызывать исключение. Он поднимается автоматически, когда функция завершается или выдаетreturn
.Это то, что делает генератор (функция, которая содержит
yield
); он начинает выполнение, делает паузу всякий раз, когда выполняет ayield
, и когда запрашивается.next()
значение, он продолжается с того места, где он был последним. По замыслу он идеально сочетается с протоколом итератора Python, который описывает, как последовательно запрашивать значения.Самым известным пользователем протокола итератора является
for
команда на Python. Итак, всякий раз, когда вы делаете:это не имеет значения , если
sequence
это список, строка, словарь или генератор объекта , как описано выше; результат тот же: вы читаете элементы из последовательности один за другим.Обратите внимание, что
def
использование функции, содержащейyield
ключевое слово, - не единственный способ создания генератора; это просто самый простой способ создать его.Для получения более точной информации читайте о типах итераторов , выражении yield и генераторах в документации по Python.
источник
Хотя многие ответы показывают, почему вы использовали бы
yield
для создания генератора, есть и другие варианты использованияyield
. Сделать сопрограмму довольно просто, что позволяет передавать информацию между двумя блоками кода. Я не буду повторять ни одного из прекрасных примеров, которые уже были даны об использованииyield
для создания генератора.Чтобы понять, что
yield
делает в следующем коде, вы можете использовать палец для отслеживания цикла по любому коду, который имеетyield
. Каждый раз, когда ваш палецyield
касается, вы должны ждать вводаnext
илиsend
ввода. Когдаnext
вызывается a , вы прослеживаете код до тех пор, пока не нажметеyield
... код справа отyield
него оценивается и возвращается вызывающей стороне ... затем вы ждете. Когдаnext
вызывается снова, вы выполняете еще один цикл по коду. Тем не менее, вы заметите, что в сопрограмме,yield
также может использоваться сsend
..., который отправит значение от вызывающей стороны в функцию выдачи. Еслиsend
дано, тоyield
получает отправленное значение и выплевывает его на левую сторону… затем трассировка в коде продолжается до тех пор, пока вы не нажметеyield
снова (возвращая значение в конце, как если бы оноnext
было вызвано).Например:
источник
Есть еще одно
yield
использование и значение (начиная с Python 3.3):От PEP 380 - Синтаксис для делегирования субгенератору :
Более того, это представит (начиная с Python 3.5):
чтобы не перепутать сопрограммы с обычным генератором (сегодня
yield
используется в обоих).источник
Все отличные ответы, однако немного сложны для новичков.
Я полагаю, вы узнали
return
утверждение.По аналогии
return
иyield
есть близнецы.return
означает «возврат и остановка», тогда как «доходность» означает «вернуться, но продолжить»Запустить его:
Видите, вы получаете только один номер, а не их список.
return
никогда не позволяет вам счастливо победить, просто реализуетесь один раз и выходите.Заменить
return
наyield
:Теперь вы выиграли, чтобы получить все цифры.
По сравнению с тем,
return
что запускается один раз и останавливается,yield
запускается запланированное вами время. Вы можете интерпретироватьreturn
какreturn one of them
иyield
какreturn all of them
. Это называетсяiterable
.Это суть о
yield
.Разница между выводом списка
return
иyield
выводом объекта :Вы всегда будете получать [0, 1, 2] из объекта списка, но только
yield
один раз сможете получить их из « вывода объекта ». Таким образом, у него есть новое имяgenerator
объекта, как показано вOut[11]: <generator object num_list at 0x10327c990>
.В заключение, в качестве метафоры, чтобы понять это:
return
иyield
близнецыlist
иgenerator
близнецыисточник
yield
. Это важно, я думаю, и должно быть выражено.Вот несколько примеров Python о том, как на самом деле реализовать генераторы, как если бы Python не предоставил им синтаксический сахар:
Как генератор Python:
Использование лексических замыканий вместо генераторов
Использование замыканий объектов вместо генераторов (потому что ClosuresAndObjectsAreEquivalent )
источник
Я собирался опубликовать «прочитайте страницу 19« Bethonley »Python: Essential Reference» для быстрого описания генераторов », но многие другие уже опубликовали хорошие описания.
Кроме того, обратите внимание, что они
yield
могут использоваться в сопрограммах как двойное их использование в функциях генератора. Хотя это не то же самое использование, что и ваш фрагмент кода,(yield)
его можно использовать как выражение в функции. Когда вызывающая сторона отправляет значение методу, используяsend()
метод, тогда сопрограмма будет выполняться до тех пор, пока(yield)
не встретится следующий оператор.Генераторы и сопрограммы - отличный способ настроить приложения типа потока данных. Я подумал, что стоило бы знать о другом использовании
yield
оператора в функциях.источник
С точки зрения программирования, итераторы реализованы в виде блоков .
Для реализации итераторов, генераторов и пулов потоков для одновременного выполнения и т. Д. В виде групповых сообщений (также называемых анонимными функциями) используются сообщения, отправляемые объекту замыкания, в котором есть диспетчер, а диспетчер отвечает на «сообщения».
http://en.wikipedia.org/wiki/Message_passing
« next » - это сообщение, отправленное закрытию, созданное « iter» вызовом ».
Есть много способов реализовать это вычисление. Я использовал мутацию, но это легко сделать без мутации, возвращая текущее значение и следующий урожай.
Вот демонстрация, которая использует структуру R6RS, но семантика абсолютно идентична Python. Это та же модель вычислений, и для ее переписывания в Python требуется только изменение синтаксиса.
источник
Вот простой пример:
Вывод:
Я не разработчик Python, но мне кажется,
yield
удерживает позицию выполнения программы, и следующий цикл начинается с позиции "yield". Кажется, что он ждет в этой позиции, и только перед этим, возвращает значение за пределами, и в следующий раз продолжает работать.Вроде бы интересная и приятная способность: D
источник
Вот мысленный образ того, что
yield
делает.Мне нравится думать о потоке как о стеке (даже если он не реализован таким образом).
Когда вызывается нормальная функция, она помещает свои локальные переменные в стек, выполняет некоторые вычисления, затем очищает стек и возвращает результат. Значения его локальных переменных больше никогда не видны.
С
yield
функцией, когда ее код начинает работать (т.е. послеnext()
вызова функции, возвращающей объект генератора, чей метод затем вызывается), он аналогичным образом помещает свои локальные переменные в стек и вычисляет некоторое время. Но затем, когда он попадает вyield
оператор, прежде чем очистить свою часть стека и вернуться, он делает снимок своих локальных переменных и сохраняет их в объекте генератора. Он также записывает место, в котором он находится в данный момент, в своем коде (то есть конкретныйyield
оператор).Так что это своего рода замороженная функция, на которой висит генератор.
Когда
next()
вызывается впоследствии, он извлекает принадлежащие функции в стек и реанимирует их. Функция продолжает вычислять с того места, где она остановилась, не обращая внимания на тот факт, что она только что провела вечность в холодильной камере.Сравните следующие примеры:
Когда мы вызываем вторую функцию, она ведет себя совершенно иначе, чем первая. Это
yield
утверждение может быть недоступно, но если оно присутствует где-либо, оно меняет природу того, с чем мы имеем дело.Вызов
yielderFunction()
не запускает свой код, но делает генератор из кода. (Может быть, это хорошая идея, чтобы назвать такие вещи сyielder
префиксом для удобства чтения.)gi_code
Иgi_frame
поле , где замороженное состояние хранится. Исследуя ихdir(..)
, мы можем подтвердить, что наша ментальная модель, представленная выше, заслуживает доверия.источник
Как и предполагает каждый ответ,
yield
используется для создания генератора последовательности. Он используется для генерации некоторой последовательности динамически. Например, читая файл построчно в сети, вы можете использоватьyield
функцию следующим образом:Вы можете использовать его в своем коде следующим образом:
Контроль исполнения передачи получил
Управление выполнением будет передано из getNextLines () в
for
цикл при выполнении yield. Таким образом, каждый раз, когда вызывается getNextLines (), выполнение начинается с того места, где оно было приостановлено в последний раз.Таким образом, вкратце, функция со следующим кодом
распечатает
источник
Простой пример, чтобы понять, что это такое:
yield
Выход:
источник
print(i, end=' ')
? В противном случае, я считаю, что поведение по умолчанию поместит каждое число в новую строку(Мой ответ ниже говорит только с точки зрения использования генератора Python, а не базовой реализации механизма генератора , который включает в себя некоторые приемы работы со стеком и кучей.)
Когда
yield
используется вместо функцииreturn
в Python, эта функция превращается во что-то специальное, называемоеgenerator function
. Эта функция вернет объектgenerator
типа. Ключевое слово флаг , чтобы уведомить питон компилятор для лечения такой функции специально. Нормальные функции завершатся, когда из него будет возвращено некоторое значение. Но с помощью компилятора функцию генератора можно считать возобновляемой. То есть контекст выполнения будет восстановлен, и выполнение будет продолжено с последнего запуска. Пока вы явно не вызовете return, что вызовет исключение (которое также является частью протокола итератора), или не достигнет конца функции. Я нашел много ссылок о , но этого одногоyield
StopIteration
generator
изfunctional programming perspective
наиболее усваивается.(Теперь я хочу поговорить об обосновании
generator
и наiterator
основе моего собственного понимания. Я надеюсь, что это поможет вам понять основную мотивацию итератора и генератора. Такая концепция проявляется и в других языках, таких как C #.)Как я понимаю, когда мы хотим обработать кучу данных, мы обычно сначала храним данные где-то, а затем обрабатываем их одну за другой. Но такой наивный подход проблематичен. Если объем данных огромен, заранее хранить их в целом дорого. Таким образом , вместо того , чтобы хранить
data
себя непосредственно, почему бы не хранить какие - тоmetadata
косвенно, то естьthe logic how the data is computed
.Существует два подхода к переносу таких метаданных.
as a class
. Это так называемый,iterator
кто реализует протокол итератора (то есть__next__()
, и__iter__()
методы). Это также часто встречающийся шаблон проектирования итераторов .as a function
. Это так называемыйgenerator function
. Но под капотом, вернулсяgenerator object
ещеIS-A
итератор , поскольку он также реализует протокол итератора.В любом случае создается итератор, то есть некоторый объект, который может дать вам нужные данные. Подход ОО может быть немного сложным. В любом случае, какой из них использовать - решать только вам.
источник
Таким образом,
yield
оператор преобразует вашу функцию в фабрику, которая создает специальный объект, называемый a,generator
который оборачивается вокруг тела вашей исходной функции. Когдаgenerator
итерация повторяется, она выполняет вашу функцию, пока не достигнет следующей,yield
затем приостанавливает выполнение и вычисляет значение, переданное ейyield
. Он повторяет этот процесс на каждой итерации, пока путь выполнения не выйдет из функции. Например,просто выводит
Питание исходит от использования генератора с циклом, который вычисляет последовательность, генератор выполняет остановку цикла каждый раз, чтобы «выдать» следующий результат вычисления, таким образом, он вычисляет список на лету, при этом преимущество заключается в памяти. сохранено для особо крупных расчетов
Скажем, вы хотите создать свою собственную
range
функцию, которая производит итеративный диапазон чисел, вы можете сделать это так,и используйте это так;
Но это неэффективно, потому что
К счастью, Гвидо и его команда были достаточно щедры на разработку генераторов, поэтому мы могли просто сделать это;
Теперь после каждой итерации функция в вызываемом генераторе
next()
выполняет функцию, пока не достигнет оператора yield, в котором она останавливается и возвращает значение, или достигает конца функции. В этом случае при первом вызовеnext()
выполняется до оператора yield и выдается «n», при следующем вызове он выполняет оператор приращения, возвращается к «времени», оценивает его, и если оно истинно, он останавливается и Если снова выдать 'n', то так будет продолжаться до тех пор, пока условие while не вернет false и генератор не перейдет к концу функции.источник
Урожай является объектом
А
return
в функции вернет одно значение.Если вы хотите, чтобы функция возвращала огромный набор значений , используйте
yield
.Более того,
yield
это барьер .То есть, он будет запускать код в вашей функции с самого начала, пока не достигнет цели
yield
. Затем он вернет первое значение цикла.Затем каждый второй вызов будет запускать цикл, который вы написали в функции, еще раз, возвращая следующее значение, пока не будет возвращено никакого значения.
источник
Многие используют,
return
а неyield
, но в некоторых случаяхyield
могут быть более эффективными и с ними легче работать.Вот пример, который
yield
определенно лучше всего подходит для:Обе функции делают одно и то же, но
yield
используют три строки вместо пяти и имеют одну переменную меньше, о которой нужно беспокоиться.Как видите, обе функции выполняют одно и то же. Разница лишь в том, что
return_dates()
дает список иyield_dates()
дает генератор.Пример из реальной жизни - что-то вроде построчного чтения файла или если вы просто хотите создать генератор.
источник
yield
это как возвращаемый элемент для функции. Разница в том, чтоyield
элемент превращает функцию в генератор. Генератор ведет себя так же, как функция, пока что-то «не получено». Генератор останавливается до следующего вызова и продолжает работу с той же точки, с которой он был запущен. Вы можете получить последовательность всех «полученных» значений в одном, вызвавlist(generator())
.источник
yield
Ключевое слово просто собирает возвращать результаты. Думатьyield
какreturn +=
источник
Вот простой
yield
основанный подход, чтобы вычислить ряд Фибоначчи, объяснил:Когда вы введете это в свой REPL, а затем попытаетесь позвонить, вы получите загадочный результат:
Это связано с тем, что
yield
в Python присутствует сигнал о том, что вы хотите создать генератор , то есть объект, который генерирует значения по требованию.Итак, как вы генерируете эти значения? Это можно сделать либо напрямую, используя встроенную функцию
next
, либо косвенно, передав ее в конструкцию, которая потребляет значения.Используя встроенную
next()
функцию, вы напрямую вызываете.next
/__next__
, заставляя генератор выдавать значение:Косвенно, если вы предоставите
fib
вfor
петлю, вlist
инициализаторе, вtuple
инициализаторе, или что - нибудь еще , что ожидает объект , который генерирует / производит значения, вы будете «потреблять» генератор до значений больше нет , может быть получено его (и он возвращается) :Аналогично с
tuple
инициализатором:Генератор отличается от функции в том смысле, что он ленив. Это достигается путем поддержания локального состояния и возобновления работы в любое время.
Когда вы впервые вызываете
fib
, вызывая его:Python компилирует функцию, находит
yield
ключевое слово и просто возвращает объект генератора обратно к вам. Не очень полезно, кажется.Когда вы затем запрашиваете, он генерирует первое значение, прямо или косвенно, он выполняет все операторы, которые он находит, пока не встретит a
yield
, он затем возвращает значение, которое вы указали,yield
и делает паузу. Для примера, который лучше демонстрирует это, давайте использовать некоторыеprint
вызовы (замените наprint "text"
if на Python 2):Теперь введите в REPL:
у вас есть объект генератора, ожидающий команду для создания значения. Используйте
next
и посмотрите, что напечатано:Результаты без кавычек - то, что напечатано. Указанный результат - это то, что возвращается
yield
. Звонитеnext
снова сейчас:Генератор помнит, что он был приостановлен,
yield value
и возобновляется оттуда. Следующее сообщение печатается, и поискyield
оператора для приостановки выполняется снова (из-заwhile
цикла).источник