Что делает ключевое слово «доходность»?
Вопрос
Какова польза от yield
ключевое слово в Python?Что оно делает?
Например, я пытаюсь понять этот код1:
def _get_child_candidates(self, distance, min_dist, max_dist):
if self._leftchild and distance - max_dist < self._median:
yield self._leftchild
if self._rightchild and distance + max_dist >= self._median:
yield self._rightchild
А это звонящий:
result, candidates = [], [self]
while candidates:
node = candidates.pop()
distance = node._get_dist(obj)
if distance <= max_dist and distance >= min_dist:
result.extend(node._values)
candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))
return result
Что происходит, когда метод _get_child_candidates
называется?Список возвращается?Один элемент?Его снова вызывают?Когда прекратятся последующие звонки?
1.Этот фрагмент кода был написан Йохеном Шульцем (jrschulz), который создал великолепную библиотеку Python для метрических пространств.Это ссылка на полный источник: Модуль mspace.
Решение
Чтобы понять, что 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...
"on является итерируемым; 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 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 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 candidates 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
операторов, примените этот простой трюк, чтобы понять, что произойдет:
- Вставить строку
result = []
в начале функции. - Замените каждый
yield expr
сresult.append(expr)
. - Вставить строку
return result
в нижней части функции. - Ура - не более
yield
заявления!Прочтите и разберитесь в коде. - Сравните функцию с исходным определением.
Этот трюк может дать вам представление о логике функции, но что на самом деле происходит с yield
существенно отличается от того, что происходит при подходе на основе списков.Во многих случаях подход доходности будет намного эффективнее и быстрее по использованию памяти.В других случаях этот трюк заставит вас застрять в бесконечном цикле, хотя исходная функция работает нормально.Читайте дальше, чтобы узнать больше...
Не путайте итераторы, итераторы и генераторы.
Во-первых, протокол итератора - когда ты пишешь
for x in mylist:
...loop body...
Python выполняет следующие два шага:
Получает итератор для
mylist
:Вызов
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
приходит в:
def f123():
yield 1
yield 2
yield 3
for item in f123():
print item
Вместо yield
заявлений, если бы у вас было три return
заявления в f123()
только первый будет выполнен, и функция завершится.Но f123()
это не обычная функция.Когда f123()
называется, это не верните любое значение в операторах доходности!Он возвращает объект-генератор.Также функция на самом деле не завершается — она переходит в приостановленное состояние.Когда for
цикл пытается пройти по объекту-генератору, функция возобновляет работу из приостановленного состояния уже на следующей строке после yield
он ранее вернулся, выполняет следующую строку кода, в данном случае yield
оператор и возвращает его в качестве следующего элемента.Это происходит до тех пор, пока функция не завершится, после чего генератор поднимает StopIteration
, и цикл завершается.
Таким образом, объект-генератор похож на адаптер: на одном конце он демонстрирует протокол итератора, предоставляя __iter__()
и next()
методы сохранения for
петля счастлива.Однако на другом конце он запускает функцию ровно настолько, чтобы получить из нее следующее значение, и переводит ее обратно в приостановленный режим.
Зачем использовать генераторы?
Обычно вы можете написать код, который не использует генераторы, но реализует ту же логику.Один из вариантов — использовать «трюк» с временным списком, о котором я упоминал ранее.Это не будет работать во всех случаях, например.если у вас бесконечные циклы, или это может привести к неэффективному использованию памяти, когда у вас очень длинный список.Другой подход — реализовать новый итерируемый класс. SomethingIter
который сохраняет состояние в членах экземпляра и выполняет следующий логический шаг в своем next()
(или __next__()
в Python 3) метод.В зависимости от логики код внутри next()
метод может оказаться очень сложным и подвержен ошибкам.Генераторы обеспечивают чистое и простое решение.
Думайте об этом так:
Итератор - это просто причудливый термин для объекта, у которого есть метод next ()
. Таким образом, функция yield-ed в итоге выглядит примерно так:
Оригинальная версия:
def some_function():
for i in xrange(4):
yield i
for i in some_function():
print i
Это в основном то, что интерпретатор Python делает с приведенным выше кодом:
class it:
def __init__(self):
# Start at -1 so that we get 0 when we add 1 below.
self.count = -1
# The __iter__ method will be called once by the 'for' loop.
# The rest of the magic happens on the object returned by this method.
# In this case it is the object itself.
def __iter__(self):
return self
# The next method will be called repeatedly by the 'for' loop
# until it raises StopIteration.
def next(self):
self.count += 1
if self.count < 4:
return self.count
else:
# A StopIteration exception is raised
# to signal that the iterator is done.
# This is caught implicitly by the 'for' loop.
raise StopIteration
def some_func():
return it()
for i in some_func():
print i
Чтобы лучше понять, что происходит за кулисами, цикл for
можно переписать так:
iterator = some_func()
try:
while 1:
print iterator.next()
except StopIteration:
pass
Это имеет больше смысла или просто сбивает вас с толку? :) Р>
Я должен отметить, что это является упрощением для иллюстративных целей. :) Р>
Ключевое слово yield
сводится к двум простым фактам:
yield
в любом месте внутри функции, эта функция больше не возвращается с помощью оператора return
. Вместо этого он немедленно возвращает ленивый " список ожидания " объект называется генератором list
или set
или range
или dict-view, со встроенным протоколом для посещения каждого элемента в определенный порядок . В двух словах: генератор - это отложенный, постепенно увеличивающийся список , а операторы yield
позволяют использовать функцию обозначения для программирования значений списка генератор должен постепенно выплевывать.
generator = myYieldingFunction(...)
x = list(generator)
generator
v
[x[0], ..., ???]
generator
v
[x[0], x[1], ..., ???]
generator
v
[x[0], x[1], x[2], ..., ???]
StopIteration exception
[x[0], x[1], x[2]] done
list==[x[0], x[1], x[2]]
<Ч>
Пример
Давайте определим функцию makeRange
, которая похожа на range
в Python. Вызов makeRange (n)
ВОЗВРАЩАЕТ ГЕНЕРАТОРА:
def makeRange(n):
# return 0,1,2,...,n-1
i = 0
while i < n:
yield i
i += 1
>>> makeRange(5)
<generator object makeRange at 0x19e4aa0>
Чтобы заставить генератор немедленно возвращать ожидающие значения, вы можете передать его в list ()
(так же, как вы могли бы повторить):
>>> list(makeRange(5))
[0, 1, 2, 3, 4]
<Ч>
Сравнение примера с "просто возвращением списка"
Приведенный выше пример можно рассматривать как простое создание списка, к которому вы добавляете и возвращаете:
# list-version # # generator-version
def makeRange(n): # def makeRange(n):
"""return [0,1,2,...,n-1]""" #~ """return 0,1,2,...,n-1"""
TO_RETURN = [] #>
i = 0 # i = 0
while i < n: # while i < n:
TO_RETURN += [i] #~ yield i
i += 1 # i += 1 ## indented
return TO_RETURN #>
>>> makeRange(5)
[0, 1, 2, 3, 4]
Однако есть одно существенное отличие; см. последний раздел.
<Ч>Как вы можете использовать генераторы
Итерация - это последняя часть понимания списка, и все генераторы итерируемы, поэтому их часто используют так:
# _ITERABLE_
>>> [x+10 for x in makeRange(5)]
[10, 11, 12, 13, 14]
Чтобы лучше понять генераторы, вы можете поиграться с модулем itertools
(обязательно используйте chain.from_iterable
вместо chain
Когда это гарантировано). Например, вы можете даже использовать генераторы для реализации бесконечно длинных ленивых списков, таких как itertools.count ()
. Вы можете реализовать собственное перечисление def enumerate (iterable): zip (count (), iterable)
или сделать это с помощью ключевого слова yield
в цикле while. р>
Обратите внимание: генераторы могут использоваться для многих других целей, таких как реализация сопрограмм или недетерминированное программирование или другие элегантные вещи. Однако «ленивые списки» точка зрения, которую я здесь представляю, является наиболее распространенным видом использования, который вы найдете.
<Ч>За кулисами
Вот как работает " протокол итераций Python " работает. То есть, что происходит, когда вы делаете список (makeRange (5))
. Это то, что я описал ранее как «ленивый, добавочный список».
>>> x=iter(range(5))
>>> next(x)
0
>>> next(x)
1
>>> next(x)
2
>>> next(x)
3
>>> next(x)
4
>>> next(x)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
Встроенная функция next ()
просто вызывает функцию объектов .next ()
, которая является частью " итерационного протокола " и встречается на всех итераторах. Вы можете вручную использовать функцию next ()
(и другие части протокола итерации) для реализации необычных вещей, обычно за счет читабельности, поэтому постарайтесь не делать этого ...
Minutiae
Обычно большинство людей не заботятся о следующих различиях и, вероятно, хотят прекратить читать здесь.
В Python-языке итерируемый - это любой объект, который "понимает концепцию цикла for "" как список [1,2,3]
, а итератор является конкретным экземпляром запрошенного цикла for, как [1,2,3]. __iter __ () код>. генератор точно такой же, как и любой итератор, за исключением того, как он был написан (с синтаксисом функции).
Когда вы запрашиваете итератор из списка, он создает новый итератор. Однако, когда вы запрашиваете итератор у итератора (что вы редко делаете), он просто дает вам свою копию.
Таким образом, в маловероятном случае, что вы не в состоянии сделать
Что это
yield
ключевое слово do в Python?
Схема ответа/сводка
- Функция с
yield
, когда его зовут, возвращает Генератор. - Генераторы являются итераторами, поскольку они реализуют протокол итератора, чтобы вы могли перебирать их.
- Генератор также может быть отправил информацию, что делает его концептуально сопрограмма.
- В Python 3 вы можете делегат от одного генератора к другому в обоих направлениях с
yield from
. - (В приложении критикуется несколько ответов, включая верхний, и обсуждается использование
return
в генераторе.)
Генераторы:
yield
допустимо только внутри определения функции, и включение yield
в определении функции заставляет ее возвращать генератор.
Идея генераторов пришла из других языков (см. сноску 1) с различными реализациями.В генераторах Python выполнение кода замороженный в точке выхода.При вызове генератора (методы обсуждаются ниже) выполнение возобновляется, а затем приостанавливается при следующем выходе.
yield
обеспечивает простой способ реализация протокола итератора, определяемый следующими двумя методами:__iter__
и next
(Питон 2) или __next__
(Питон 3).Оба эти метода делают объект итератором, который вы могли бы проверить с помощью Iterator
Абстрактный базовый класс из collections
модуль.
>>> def func():
... yield 'I am'
... yield 'a generator!'
...
>>> type(func) # A function with yield is still a function
<type 'function'>
>>> gen = func()
>>> type(gen) # but it returns a generator
<type 'generator'>
>>> hasattr(gen, '__iter__') # that's an iterable
True
>>> hasattr(gen, 'next') # and with .next (.__next__ in Python 3)
True # implements the iterator protocol.
Тип генератора является подтипом итератора:
>>> import collections, types
>>> issubclass(types.GeneratorType, collections.Iterator)
True
И если необходимо, мы можем выполнить проверку типов следующим образом:
>>> isinstance(gen, types.GeneratorType)
True
>>> isinstance(gen, collections.Iterator)
True
Особенность Iterator
это когда-то исчерпано, вы не можете повторно использовать или сбросить его:
>>> list(gen)
['I am', 'a generator!']
>>> list(gen)
[]
Вам придется создать еще один, если вы хотите снова использовать его функциональность (см. сноску 2):
>>> list(func())
['I am', 'a generator!']
Данные можно получить программно, например:
def func(an_iterable):
for item in an_iterable:
yield item
Вышеупомянутый простой генератор также эквивалентен приведенному ниже: начиная с Python 3.3 (и недоступен в Python 2), вы можете использовать yield from
:
def func(an_iterable):
yield from an_iterable
Однако, yield from
Также позволяет делегировать субгрогрогаторам, что будет объяснено в следующем разделе о кооперативной делегировании с подкорутинами.
Сопрограммы:
yield
формирует выражение, позволяющее отправлять данные в генератор (см. сноску 3)
Вот пример, обратите внимание received
переменная, которая будет указывать на данные, отправляемые в генератор:
def bank_account(deposited, interest_rate):
while True:
calculated_interest = interest_rate * deposited
received = yield calculated_interest
if received:
deposited += received
>>> my_account = bank_account(1000, .05)
Во-первых, мы должны поставить генератор в очередь со встроенной функцией: next
.Это позвонит соответствующим next
или __next__
Метод, в зависимости от версии Python, которую вы используете:
>>> first_year_interest = next(my_account)
>>> first_year_interest
50.0
И теперь мы можем отправлять данные в генератор.(Отправка None
это то же самое, что и призыв next
.) :
>>> next_year_interest = my_account.send(first_year_interest + 1000)
>>> next_year_interest
102.5
Совместное делегирование в подпрограмму с yield from
Теперь вспомните, что yield from
доступен в Python 3.Это позволяет нам делегировать Coroutines в подкору:
def money_manager(expected_rate):
under_management = yield # must receive deposited value
while True:
try:
additional_investment = yield expected_rate * under_management
if additional_investment:
under_management += additional_investment
except GeneratorExit:
'''TODO: write function to send unclaimed funds to state'''
finally:
'''TODO: write function to mail tax info to client'''
def investment_account(deposited, manager):
'''very simple model of an investment account that delegates to a manager'''
next(manager) # must queue up manager
manager.send(deposited)
while True:
try:
yield from manager
except GeneratorExit:
return manager.close()
И теперь мы можем делегировать функциональность в подгенератор, и он может использоваться генератором, как и выше:
>>> my_manager = money_manager(.06)
>>> my_account = investment_account(1000, my_manager)
>>> first_year_return = next(my_account)
>>> first_year_return
60.0
>>> next_year_return = my_account.send(first_year_return + 1000)
>>> next_year_return
123.6
Вы можете узнать больше о точной семантике yield from
в ПЭП 380.
Другие методы:закрыть и бросить
А close
метод вызывает GeneratorExit
В тот момент выполнение функции было заморожено.Это также будет вызвано __del__
Таким образом, вы можете поместить любой код очистки, где вы обрабатываете GeneratorExit
:
>>> my_account.close()
Вы также можете бросить исключение, которое можно обработать в генераторе или распространять обратно к пользователю:
>>> import sys
>>> try:
... raise ValueError
... except:
... my_manager.throw(*sys.exc_info())
...
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
File "<stdin>", line 2, in <module>
ValueError
Заключение
Я считаю, что охватил все аспекты следующего вопроса:
Что это
yield
ключевое слово do в Python?
Оказывается, что yield
делает много.Я уверен, что могу добавить еще более тщательные примеры в это.Если вы хотите большего или имеете какую -то конструктивную критику, дайте мне знать, комментируя ниже.
Приложение:
Критика лучшего/принятого ответа**
- Он не понимает, что делает повторяемый, просто используя список в качестве примера.См. мои ссылки выше, но вкратце:итерация имеет
__iter__
метод, возвращающий итератор.Ан итератор обеспечивает.next
(Python 2 или.__next__
(Python 3), который неявно вызываетсяfor
петли, пока не подниметсяStopIteration
, и как только это произойдет, оно будет продолжать это делать. - Затем он использует выражение генератора, чтобы описать, что такое генератор.Поскольку генератор — это просто удобный способ создать итератор, это только запутывает дело, а мы еще не дошли до
yield
часть. - В Контроль усталости генератора он называет
.next
метод, когда вместо этого ему следует использовать встроенную функцию,next
.Это был бы подходящий уровень косвенности, поскольку его код не работает в Python 3. - Итертулс?Это не имело отношения к тому, что
yield
вообще делает. - Никакого обсуждения методов, которые
yield
предоставляет наряду с новой функциональностьюyield from
в Python 3. Самый популярный/принятый ответ – очень неполный ответ.
Критика ответа, предлагающего yield
в выражении или понимании генератора.
Грамматика в настоящее время допускает любое выражение в понимании списка.
expr_stmt: testlist_star_expr (annassign | augassign (yield_expr|testlist) |
('=' (yield_expr|testlist_star_expr))*)
...
yield_expr: 'yield' [yield_arg]
yield_arg: 'from' test | testlist
Поскольку выход — это выражение, некоторые рекламировали его как интересное использование в выражениях понимания или генераторах — несмотря на то, что не было указано ни одного особенно хорошего варианта использования.
Разработчики ядра CPython обсуждение отмены его пособия.Вот соответствующее сообщение из списка рассылки:
30 января 2017 в 19:05 Бретт Кэннон написал:
В Вс, 29 Янв 2017 в 16:39 Крейг Родригес написал:
Меня устраивает любой подход.Оставить вещи такими, какими они находятся в Python 3, не очень хорошо, ИМХО.
Мой голос - это синтаксис, так как вы не получаете то, что ожидаете от синтаксиса.
Я бы согласился, что это разумное место для нас, чтобы оказаться, так как любой код, полагаясь на текущее поведение, действительно слишком умно, чтобы быть обслуживаемым.
Что касается достижения этой цели, нам, вероятно, понадобится:
- SyntaxWarning или DeprecationWarning в версии 3.7
- Предупреждение Py3k в версии 2.7.x
- Синтаксическая ошибка в 3.8
Приветствую, Ник.
- Ник Коглан | ncoghlan на gmail.com | Брисбен, Австралия
Далее, существует нерешенный выпуск (10544) что, кажется, указывает в этом направлении никогда это хорошая идея (PyPy, реализация Python, написанная на Python, уже выдает синтаксические предупреждения.)
Итог, пока разработчики CPython не скажут нам обратное: Не ставь yield
в выражении или понимании генератора.
А return
заявление в генераторе
В Питон 2:
В функции генератора
return
заявление не может включать в себяexpression_list
.В этом контексте голыйreturn
указывает на то, что генератор завершен и вызоветStopIteration
быть воспитанным.
Ан expression_list
по сути, это любое количество выражений, разделенных запятыми. По сути, в Python 2 вы можете остановить генератор с помощью return
, но вы не можете вернуть значение.
В Питон 3:
В функции генератора
return
Оператор указывает, что генератор завершен и вызоветStopIteration
быть воспитанным.Возвращаемое значение (если есть) используется в качестве аргумента для построенияStopIteration
и становитсяStopIteration.value
атрибут.
Сноски
Языки CLU, Sather и Icon были упомянуты в предложении представить концепцию генераторов в Python.Общая идея заключается в том, что функция может поддерживать внутреннее состояние и давать промежуточные точки данных по требованию пользователем.Это обещало быть превосходный в производительности с другими подходами, включая резьбу для питона, который даже недоступен в некоторых системах.
Это означает, например, что
xrange
объекты (range
в Python 3) нетIterator
s, даже несмотря на то, что они повторяемые, поскольку их можно использовать повторно.Как и списки, их__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
вызывает итератор, пока он не будет исчерпан. В случае с примером кода, который вы разместили, было бы намного проще просто вернуть кортеж и добавить его в список.
Есть еще одна вещь, которую стоит упомянуть: функция, которая возвращает результат, на самом деле не должна завершаться. Я написал такой код:
def fib():
last, cur = 0, 1
while True:
yield cur
last, cur = cur, last + cur
Тогда я могу использовать его в другом коде, например так:
for f in fib():
if some_condition: break
coolfuncs(f);
Это действительно помогает упростить некоторые проблемы и облегчает работу с некоторыми вещами. Р>
Для тех, кто предпочитает минимальный рабочий пример, медитируйте на этом интерактивном сеансе Python:
>>> def f():
... yield 1
... yield 2
... yield 3
...
>>> g = f()
>>> for i in g:
... print i
...
1
2
3
>>> for i in g:
... print i
...
>>> # Note that this time nothing was printed
ТЛ;ДР
Вместо этого:
def square_list(n):
the_list = [] # Replace
for x in range(n):
y = x * x
the_list.append(y) # these
return the_list # lines
сделай это:
def square_yield(n):
for x in range(n):
y = x * x
yield y # with this one.
Всякий раз, когда вы обнаруживаете, что составляете список с нуля, yield
каждый кусок вместо этого.
Это был мой первый момент «ага» с выходом.
yield
это сладкий способ сказать
создать серию вещей
То же поведение:
>>> for square in square_list(4):
... print(square)
...
0
1
4
9
>>> for square in square_yield(4):
... print(square)
...
0
1
4
9
Различное поведение:
Доходность один проход:вы можете выполнить итерацию только один раз.Когда функция имеет доходность, мы называем ее функция генератора.И итератор это то, что он возвращает.Эти термины показательны.Мы теряем удобство контейнера, но получаем мощь ряда, вычисляемого по мере необходимости и произвольной длины.
Доходность ленивый, это откладывает вычисления.Функция с доходностью в ней на самом деле вообще не выполняется, когда вы его вызываете. Он возвращает объект итератора который помнит, где он остановился.Каждый раз, когда ты звонишь next()
на итераторе (это происходит в цикле for) выполнение идет на несколько дюймов вперед к следующему выходу. return
вызывает StopIteration и завершает серию (это естественный конец цикла for).
Доходность универсальный.Данные не обязательно должны храниться все вместе, их можно предоставлять по одному.Оно может быть бесконечным.
>>> def squares_all_of_them():
... x = 0
... while True:
... yield x * x
... x += 1
...
>>> squares = squares_all_of_them()
>>> for _ in range(4):
... print(next(squares))
...
0
1
4
9
Если тебе надо несколько проходов и серия не слишком длинная, просто позвоните list()
в теме:
>>> list(square_yield(4))
[0, 1, 4, 9]
Блестящий выбор слова yield
потому что оба значения применять:
урожай — производить или предоставлять (как в сельском хозяйстве)
...предоставьте следующие данные в серии.
урожай - уступить дорогу или отказаться (как в случае с политической властью)
...отказаться от выполнения процессора до тех пор, пока итератор не продвинется вперед.
Доходность дает вам генератор. Р>
def get_odd_numbers(i):
return range(1, i, 2)
def yield_odd_numbers(i):
for x in range(1, i, 2):
yield x
foo = get_odd_numbers(10)
bar = yield_odd_numbers(10)
foo
[1, 3, 5, 7, 9]
bar
<generator object yield_odd_numbers at 0x1029c6f50>
bar.next()
1
bar.next()
3
bar.next()
5
Как видите, в первом случае foo
хранит весь список в памяти сразу. Это не имеет большого значения для списка из 5 элементов, но что, если вы хотите список из 5 миллионов? Мало того, что это большой пожиратель памяти, он также требует много времени для создания во время вызова функции.
Во втором случае bar
просто дает вам генератор. Генератор является итеративным - это означает, что вы можете использовать его в цикле for
и т. Д., Но каждое значение может быть доступно только один раз. Все значения также не сохраняются в памяти одновременно; объект генератора "запоминает" где он был в цикле в последний раз, когда вы его вызывали - таким образом, если вы используете итеративный подсчет (скажем) до 50 миллиардов, вам не нужно считать до 50 миллиардов одновременно и сохранять 50 миллиард цифр для подсчета.
Опять же, это довольно надуманный пример, вы, вероятно, использовали бы itertools, если бы вы действительно хотели сосчитать до 50 миллиардов. :) Р>
Это самый простой вариант использования генераторов. Как вы сказали, его можно использовать для написания эффективных перестановок, используя yield для продвижения по стеку вызовов вместо использования некоторой переменной стека. Генераторы также могут быть использованы для специализированного обхода дерева и всего прочего.
Он возвращает генератор. Я не особенно знаком с Python, но я считаю, что это то же самое, что и блоки итераторов C # если вы знакомы с ними.
Ключевая идея заключается в том, что компилятор / интерпретатор / что-либо делает некоторую хитрость, чтобы вызывающий объект мог продолжать вызывать next (), и он продолжит возвращать значения - , как если бы метод генератора был помолчал . Теперь очевидно, что вы не можете «приостановить» метод, поэтому компилятор создает конечный автомат, чтобы вы могли запомнить, где вы находитесь в данный момент, как выглядят локальные переменные и т. д. Это гораздо проще, чем написать итератор самостоятельно.
Среди множества отличных ответов, описывающих, как использовать генераторы, есть один тип ответа, который, по моему мнению, еще не был дан.Вот ответ теории языка программирования:
А yield
Оператор в Python возвращает генератор.Генератор в Python — это функция, которая возвращает продолжения (и, в частности, тип сопрограммы, но продолжения представляют собой более общий механизм понимания того, что происходит).
Продолжения в теории языков программирования представляют собой гораздо более фундаментальный вид вычислений, но они используются нечасто, поскольку их чрезвычайно сложно рассуждать, а также очень сложно реализовать.Но идея продолжения проста:это состояние вычисления, которое еще не завершено.В этом состоянии сохраняются текущие значения переменных, операции, которые еще предстоит выполнить, и так далее.Затем в какой-то момент программы можно вызвать продолжение, так что переменные программы будут сброшены в это состояние и будут выполнены сохраненные операции.
Продолжения в этой более общей форме могут быть реализованы двумя способами.в call/cc
Таким образом, стек программы буквально сохраняется, а затем, когда вызывается продолжение, стек восстанавливается.
В стиле передачи продолжения (CPS) продолжения представляют собой обычные функции (только в языках, где функции относятся к первому классу), которыми программист явно управляет и передает подпрограммам.В этом стиле состояние программы представляется замыканиями (и переменными, которые в них закодированы), а не переменными, которые находятся где-то в стеке.Функции, которые управляют потоком управления, принимают продолжение в качестве аргументов (в некоторых вариантах CPS функции могут принимать несколько продолжений) и управляют потоком управления, вызывая их, просто вызывая их и возвращаясь после этого.Очень простой пример стиля передачи продолжения выглядит следующим образом:
def save_file(filename):
def write_file_continuation():
write_stuff_to_file(filename)
check_if_file_exists_and_user_wants_to_overwrite(write_file_continuation)
В этом (очень упрощенном) примере программист сохраняет операцию фактической записи файла в продолжение (что потенциально может быть очень сложной операцией с множеством деталей для записи), а затем передает это продолжение (т. е. в качестве первого файла). закрытие класса) другому оператору, который выполняет дополнительную обработку, а затем вызывает его при необходимости.(Я часто использую этот шаблон проектирования в реальном программировании с графическим интерфейсом, либо потому, что он экономит мне строки кода, либо, что более важно, для управления потоком управления после запуска событий графического интерфейса.)
Оставшаяся часть этого поста, без потери общности, будет концептуализировать продолжения как CPS, потому что их намного легче понять и прочитать.
Теперь поговорим о генераторах в Python.Генераторы — это особый подтип продолжения.Тогда как продолжения вообще способны сохранять состояние вычисление (т. е. стек вызовов программы), генераторы способны сохранять состояние итерации только в течение итератор.Хотя это определение немного вводит в заблуждение в некоторых случаях использования генераторов.Например:
def f():
while True:
yield 4
Это, очевидно, разумная итерация, поведение которой четко определено — каждый раз, когда генератор выполняет итерацию по ней, он возвращает 4 (и делает это всегда).Но, вероятно, это не тот прототипный тип итерации, который приходит на ум, когда мы думаем об итераторах (т. е. for x in collection: do_something(x)
).Этот пример иллюстрирует мощность генераторов:если что-то является итератором, генератор может сохранить состояние своей итерации.
Повторить:Продолжения могут сохранять состояние стека программы, а генераторы — состояние итерации.Это означает, что продолжения более мощны, чем генераторы, но также и то, что генераторы намного проще.Их легче реализовать разработчику языка и проще использовать программисту (если у вас есть немного времени, попробуйте прочитать и понять эта страница о продолжениях и вызове/cc).
Но вы можете легко реализовать (и концептуализировать) генераторы как простой, конкретный случай стиля передачи продолжения:
В любое время yield
вызывается, он сообщает функции вернуть продолжение.Когда функция вызывается снова, она начинается с того места, где остановилась.Итак, в псевдопсевдокоде (т.е. не псевдокоде, а не коде) генератор next
метод в основном следующий:
class Generator():
def __init__(self,iterable,generatorfun):
self.next_continuation = lambda:generatorfun(iterable)
def next(self):
value, next_continuation = self.next_continuation()
self.next_continuation = next_continuation
return value
где yield
Ключевое слово на самом деле является синтаксическим сахаром для реальной функции генератора, в основном что-то вроде:
def generatorfun(iterable):
if len(iterable) == 0:
raise StopIteration
else:
return (iterable[0], lambda:generatorfun(iterable[1:]))
Помните, что это всего лишь псевдокод, а реальная реализация генераторов в Python более сложна.Но в качестве упражнения, чтобы понять, что происходит, попробуйте использовать стиль передачи продолжения для реализации объектов-генераторов без использования метода yield
ключевое слово.
Вот пример простым языком.Я предоставлю соответствие между человеческими концепциями высокого уровня и концепциями Python низкого уровня.
Я хочу работать с последовательностью чисел, но не хочу утруждать себя созданием этой последовательности, я хочу только сосредоточиться на операции, которую хочу выполнить.Итак, я делаю следующее:
- Я звоню вам и говорю, что мне нужна последовательность чисел, полученная определенным образом, и сообщаю вам, каков алгоритм.
Этот шаг соответствуетdef
включение функции генератора, т.е.функция, содержащаяyield
. - Через некоторое время я говорю вам: «Хорошо, приготовьтесь назвать мне последовательность чисел».
Этот шаг соответствует вызову функции-генератора, которая возвращает объект-генератор. Обратите внимание, что вы пока не сообщаете мне никаких цифр;вы просто берете бумагу и карандаш. - Я прошу вас: «Назовите мне следующее число», а вы говорите мне первое число;после этого ты ждешь, пока я спрошу у тебя следующий номер.Ваша задача — запомнить, где вы были, какие цифры уже произнесли и какое следующее число.Меня не волнуют детали.
Этот шаг соответствует вызову.next()
на объекте-генераторе. - … повторяйте предыдущий шаг, пока…
- в конце концов, вам может прийти конец.Вы не говорите мне номер;вы просто кричите: «Придержите лошадей!Я задолбался!Никаких больше цифр!»
Этот шаг соответствует завершению работы объекта-генератора и поднятиюStopIteration
исключение Функция генератора не должна вызывать исключение.Он вызывается автоматически, когда функция завершается или выдает ошибку.return
.
Это то, что делает генератор (функция, содержащая yield
);он начинает выполняться, приостанавливается каждый раз, когда он выполняет yield
, и когда его попросили .next()
значение, оно продолжается с той точки, где оно было последним.По своей конструкции он идеально сочетается с протоколом итератора Python, который описывает, как последовательно запрашивать значения.
Самый известный пользователь протокола итератора — for
команда на Python.Итак, всякий раз, когда вы делаете:
for item in sequence:
не имеет значения, если sequence
это список, строка, словарь или генератор объект как описано выше;результат тот же:вы читаете элементы последовательности один за другим.
Обратите внимание, что def
создание функции, которая содержит yield
ключевое слово — не единственный способ создать генератор;это просто самый простой способ создать его.
Для более точной информации читайте о типы итераторов, заявление о доходности и генераторы в документации Python.
Хотя многие ответы показывают, почему вы используете yield
для создания генератора, есть и другие варианты использования yield
. Сделать сопрограмму довольно просто, что позволяет передавать информацию между двумя блоками кода. Я не буду повторять ни одного из прекрасных примеров, которые уже были приведены об использовании yield
для создания генератора.
Чтобы понять, что делает yield
в следующем коде, вы можете использовать палец для отслеживания цикла по любому коду, который имеет yield
. Каждый раз, когда ваш палец нажимает yield
, вы должны ждать ввода next
или send
. Когда вызывается next
, вы прослеживаете код, пока не нажмете yield
& # 8230; код справа от yield
оценивается и возвращается вызывающей стороне & # 8230; тогда подожди. Когда next
вызывается снова, вы выполняете еще один цикл по коду. Однако вы заметите, что в сопрограмме yield
также можно использовать с send
& # 8230; который отправит значение из вызывающего в функцию возврата. Если задано send
, то yield
получает отправленное значение и выплевывает его в левую часть & # 8230; затем трассировка кода продолжается до тех пор, пока вы снова не нажмете yield
(возвращает значение в конце, как если бы был вызван next
).
Например:
>>> def coroutine():
... i = -1
... while True:
... i += 1
... val = (yield i)
... print("Received %s" % val)
...
>>> sequence = coroutine()
>>> sequence.next()
0
>>> sequence.next()
Received None
1
>>> sequence.send('hello')
Received hello
2
>>> sequence.close()
Существует другое использование и значение yield
(начиная с Python 3.3):
yield from <expr>
От PEP 380 - Синтаксис для делегирования подгруппе
Синтаксис предлагается для генератора, чтобы делегировать часть своих операций другому генератору. Это позволяет разделить код, содержащий «yield», и поместить его в другой генератор. Кроме того, субгенератору разрешено возвращать со значением, а значение становится доступным для делегирующего генератора.
Новый синтаксис также открывает некоторые возможности для оптимизации, когда один генератор повторно возвращает значения, созданные другим.
Более того, это представит (начиная с Python 3.5):
async def new_coroutine(data):
...
await blocking_action()
чтобы не спутать сопрограммы с обычным генератором (сегодня в обоих используется yield
).
Все отличные ответы, однако немного сложны для новичков.
Я предполагаю, что вы узнали return
заявление.
В качестве аналогии, return
и yield
Близнецы. return
означает «вернуться и остановиться», тогда как «уступить» означает «вернуться, но продолжать».
- Попробуйте получить num_list с помощью
return
.
def num_list(n):
for i in range(n):
return i
Запустить его:
In [5]: num_list(3)
Out[5]: 0
Видите ли, вы получаете только одно число, а не их список. return
никогда не позволит вам счастливо одержать победу, просто реализует один раз и уходит.
- Наступает
yield
Заменять return
с yield
:
In [10]: def num_list(n):
...: for i in range(n):
...: yield i
...:
In [11]: num_list(3)
Out[11]: <generator object num_list at 0x10327c990>
In [12]: list(num_list(3))
Out[12]: [0, 1, 2]
Теперь вы выиграли и получили все числа.
По сравнению с return
который запускается один раз и останавливается, yield
пробегает запланированное время.Вы можете интерпретировать return
как return one of them
, и yield
как return all of them
.Это называется iterable
.
- Еще один шаг, который мы можем переписать
yield
заявление сreturn
In [15]: def num_list(n):
...: result = []
...: for i in range(n):
...: result.append(i)
...: return result
In [16]: num_list(3)
Out[16]: [0, 1, 2]
Это суть yield
.
Разница между списком return
выходы и объект yield
вывод:
Вы всегда будете получать [0, 1, 2] из объекта списка, но можете получить их только из «объекта». yield
вывод» один раз.Итак, у него новое имя generator
объект, как показано на Out[11]: <generator object num_list at 0x10327c990>
.
В заключение, как метафора для понимания:
return
иyield
Близнецыlist
иgenerator
Близнецы
Вот несколько примеров Python о том, как на самом деле реализовать генераторы, как если бы Python не предоставил им синтаксический сахар:
Как генератор Python:
from itertools import islice
def fib_gen():
a, b = 1, 1
while True:
yield a
a, b = b, a + b
assert [1, 1, 2, 3, 5] == list(islice(fib_gen(), 5))
Использование лексических замыканий вместо генераторов
def ftake(fnext, last):
return [fnext() for _ in xrange(last)]
def fib_gen2():
#funky scope due to python2.x workaround
#for python 3.x use nonlocal
def _():
_.a, _.b = _.b, _.a + _.b
return _.a
_.a, _.b = 0, 1
return _
assert [1,1,2,3,5] == ftake(fib_gen2(), 5)
Использование замыканий объектов вместо генераторов (потому что ClosuresAndObjectsAreEquivalent ) р>
class fib_gen3:
def __init__(self):
self.a, self.b = 1, 1
def __call__(self):
r = self.a
self.a, self.b = self.b, self.a + self.b
return r
assert [1,1,2,3,5] == ftake(fib_gen3(), 5)
Я собирался опубликовать " прочитайте страницу 19 'Bethonley' Python: Essential Reference 'для быстрого описания генераторов " ;, но многие другие уже опубликовали хорошие описания.
Также обратите внимание, что yield
может использоваться в сопрограммах как двойное их использование в функциях генератора. Хотя это не то же самое использование, что и ваш фрагмент кода, (yield)
может использоваться как выражение в функции. Когда вызывающая сторона отправляет значение в метод с использованием метода send ()
, сопрограмма будет выполняться до тех пор, пока не встретится следующая инструкция (yield)
.
Генераторы и сопрограммы - отличный способ настроить приложения типа потока данных. Я подумал, что стоит знать о другом использовании оператора yield
в функциях.
С точки зрения программирования итераторы реализованы как спасибо.
Для реализации итераторов, генераторов и пулов потоков для одновременного выполнения и т. д.в качестве переходников (также называемых анонимными функциями) используются сообщения, отправляемые объекту замыкания, у которого есть диспетчер, и диспетчер отвечает на «сообщения».
http://en.wikipedia.org/wiki/Message_passing
"следующий" — это сообщение, отправленное на замыкание, созданное "итер" вызов.
Существует множество способов реализовать это вычисление.Я использовал мутацию, но это легко сделать и без мутации, вернув текущее значение и следующий доход.
Вот демонстрация, в которой используется структура R6RS, но семантика абсолютно идентична семантике Python.Это та же самая модель вычислений, и для ее перезаписи на Python требуется лишь изменение синтаксиса.
Welcome to Racket v6.5.0.3. -> (define gen (lambda (l) (define yield (lambda () (if (null? l) 'END (let ((v (car l))) (set! l (cdr l)) v)))) (lambda(m) (case m ('yield (yield)) ('init (lambda (data) (set! l data) 'OK)))))) -> (define stream (gen '(1 2 3))) -> (stream 'yield) 1 -> (stream 'yield) 2 -> (stream 'yield) 3 -> (stream 'yield) 'END -> ((stream 'init) '(a b)) 'OK -> (stream 'yield) 'a -> (stream 'yield) 'b -> (stream 'yield) 'END -> (stream 'yield) 'END ->
Вот простой пример:
def isPrimeNumber(n):
print "isPrimeNumber({}) call".format(n)
if n==1:
return False
for x in range(2,n):
if n % x == 0:
return False
return True
def primes (n=1):
while(True):
print "loop step ---------------- {}".format(n)
if isPrimeNumber(n): yield n
n += 1
for n in primes():
if n> 10:break
print "wiriting result {}".format(n)
Выход:
loop step ---------------- 1
isPrimeNumber(1) call
loop step ---------------- 2
isPrimeNumber(2) call
loop step ---------------- 3
isPrimeNumber(3) call
wiriting result 3
loop step ---------------- 4
isPrimeNumber(4) call
loop step ---------------- 5
isPrimeNumber(5) call
wiriting result 5
loop step ---------------- 6
isPrimeNumber(6) call
loop step ---------------- 7
isPrimeNumber(7) call
wiriting result 7
loop step ---------------- 8
isPrimeNumber(8) call
loop step ---------------- 9
isPrimeNumber(9) call
loop step ---------------- 10
isPrimeNumber(10) call
loop step ---------------- 11
isPrimeNumber(11) call
Я не разработчик Python, но мне кажется yield
удерживает позицию выполнения программы, а следующий цикл начинается с позиции «выход».Такое ощущение, что он ждет в этой позиции, а перед этим возвращает значение наружу и в следующий раз продолжает работать.
Кажется, это интересная и приятная способность :D
Вот мысленное представление о том, что делает yield
.
Мне нравится думать о потоке как о стеке (даже если он не реализован таким образом).
Когда вызывается нормальная функция, она помещает свои локальные переменные в стек, выполняет некоторые вычисления, затем очищает стек и возвращает результат. Значения его локальных переменных больше никогда не видны.
С помощью функции yield
, когда ее код начинает выполняться (т. е. после вызова функции, возвращается объект генератора, чей метод next ()
затем вызывается) он также помещает свои локальные переменные в стек и некоторое время вычисляет. Но затем, когда он достигает оператора yield
, перед очисткой своей части стека и возвратом он делает снимок своих локальных переменных и сохраняет их в объекте генератора. Он также записывает в своем коде место, где он находится в данный момент (то есть конкретный оператор yield
).
Так что это своего рода замороженная функция, на которой висит генератор.
Когда next ()
вызывается впоследствии, он извлекает принадлежащие функции в стек и реанимирует их. Функция продолжает вычислять с того места, где она остановилась, не обращая внимания на тот факт, что она только что провела вечность в холодильной камере.
Сравните следующие примеры:
def normalFunction():
return
if False:
pass
def yielderFunction():
return
if False:
yield 12
Когда мы вызываем вторую функцию, она ведет себя совершенно иначе, чем первая. Оператор yield
может быть недоступен, но если он присутствует где-либо, он меняет природу того, с чем мы имеем дело.
>>> yielderFunction()
<generator object yielderFunction at 0x07742D28>
Вызов yielderFunction ()
не запускает свой код, но делает генератор из кода. (Может быть, это хорошая идея называть такие вещи префиксом yielder
для удобства чтения.)
>>> gen = yielderFunction()
>>> dir(gen)
['__class__',
...
'__iter__', #Returns gen itself, to make it work uniformly with containers
... #when given to a for loop. (Containers return an iterator instead.)
'close',
'gi_code',
'gi_frame',
'gi_running',
'next', #The method that runs the function's body.
'send',
'throw']
В полях gi_code
и gi_frame
хранится замороженное состояние. Исследуя их с помощью dir (..)
, мы можем подтвердить, что наша ментальная модель, представленная выше, заслуживает доверия.
Как и предполагает каждый ответ, yield
используется для создания генератора последовательности. Он используется для генерации некоторой последовательности динамически. Например, читая файл построчно в сети, вы можете использовать функцию yield
следующим образом:
def getNextLines():
while con.isOpen():
yield con.read()
Вы можете использовать его в своем коде следующим образом:
for line in getNextLines():
doSomeThing(line)
Перевод управления выполнением получил
Контроль выполнения будет перенесен из getNextLines () в цикл for
при выполнении yield. Таким образом, каждый раз, когда вызывается getNextLines (), выполнение начинается с того места, где оно было приостановлено в последний раз.
Таким образом, вкратце, функция со следующим кодом
def simpleYield():
yield "first time"
yield "second time"
yield "third time"
yield "Now some useful value {}".format(12)
for i in simpleYield():
print i
распечатает
"first time"
"second time"
"third time"
"Now some useful value 12"
Доход - это объект
return
в функции вернет одно значение.
Если вы хотите, чтобы функция возвращала огромный набор значений , используйте yield
.
Что еще более важно, yield
является барьером .
как барьер в языке CUDA, он не будет передавать управление, пока не получит завершено.
То есть, он будет запускать код в вашей функции с самого начала, пока не достигнет yield
. Затем он вернет первое значение цикла.
Затем каждый второй вызов будет запускать цикл, который вы написали в функции, еще раз, возвращая следующее значение, пока не будет возвращено никакого значения.
(Мой ответ ниже говорит только с точки зрения использования генератора Python, а не с точки зрения базовая реализация механизма генератора, который включает в себя некоторые приемы манипуляций со стеком и кучей.)
Когда yield
используется вместо return
в функции Python эта функция превращается во что-то особенное, называемое generator function
.Эта функция вернет объект generator
тип. А yield
Ключевое слово — это флаг, уведомляющий компилятор Python о необходимости особого обращения с такой функцией. Обычные функции завершатся, как только из них будет возвращено какое-то значение.Но с помощью компилятора функция генератора можно подумать как возобновляемый.То есть контекст выполнения будет восстановлен, и выполнение продолжится с последнего запуска.Пока вы явно не вызовете return, что вызовет 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
оператор преобразует вашу функцию в фабрику, которая создает специальный объект, называемый generator
который окружает тело исходной функции.Когда generator
повторяется, он выполняет вашу функцию, пока не достигнет следующего yield
затем приостанавливает выполнение и вычисляет значение, переданное в yield
.Этот процесс повторяется на каждой итерации до тех пор, пока путь выполнения не выйдет из функции.Например,
def simple_generator():
yield 'one'
yield 'two'
yield 'three'
for i in simple_generator():
print i
просто выводит
one
two
three
Мощность достигается за счет использования генератора с циклом, который вычисляет последовательность, генератор выполняет цикл, останавливаясь каждый раз, чтобы «выдать» следующий результат расчета, таким образом, он вычисляет список на лету, преимуществом является память сохраняется для особо больших вычислений
Допустим, вы хотите создать свой собственный range
функцию, которая создает повторяемый диапазон чисел, вы можете сделать это так,
def myRangeNaive(i):
n = 0
range = []
while n < i:
range.append(n)
n = n + 1
return range
и используйте его вот так;
for i in myRangeNaive(10):
print i
Но это неэффективно, потому что
- Вы создаете массив, который используете только один раз (это тратит память).
- Этот код фактически дважды проходит по этому массиву!:(
К счастью, Гвидо и его команда были достаточно щедры, чтобы разработать генераторы, и мы могли просто сделать это;
def myRangeSmart(i):
n = 0
while n < i:
yield n
n = n + 1
return
for i in myRangeSmart(10):
print i
Теперь на каждой итерации функция генератора, называемая next()
выполняет функцию до тех пор, пока она либо не достигнет оператора «yield», в котором она остановится и «выдаст» значение, либо не достигнет конца функции.В этом случае при первом звонке next()
выполняется до оператора доходности и выдает 'n', при следующем вызове он выполняет оператор инкремента, возвращается к оператору ' while', оценивает его, и если это правда, он останавливается и снова выдает 'n', он будет продолжайте в том же духе до тех пор, пока условие while не вернет false и генератор не перейдет к концу функции.
Многие люди используют return
вместо yield
, но в некоторых случаях yield
может быть более эффективным и с ним легче работать.
Вот пример, для которого yield
определенно лучше всего подходит:
возврат (в функции)
import random
def return_dates():
dates = [] # With 'return' you need to create a list then return it
for i in range(5):
date = random.choice(["1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th", "10th"])
dates.append(date)
return dates
доход (в функции)
def yield_dates():
for i in range(5):
date = random.choice(["1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th", "10th"])
yield date # 'yield' makes a generator automatically which works
# in a similar way. This is much more efficient.
Вызов функций
dates_list = return_dates()
print(dates_list)
for i in dates_list:
print(i)
dates_generator = yield_dates()
print(dates_generator)
for i in dates_generator:
print(i)
Обе функции выполняют одно и то же, но yield
использует три строки вместо пяти и имеет на одну переменную меньше, о которой нужно беспокоиться.
Это результат кода:
Как видите, обе функции выполняют одно и то же. Единственное отличие состоит в том, что return_dates ()
предоставляет список, а yield_dates ()
- генератор.
Пример из реальной жизни - это что-то вроде построчного чтения файла или если вы просто хотите создать генератор.
yield
похож на элемент возврата для функции. Разница в том, что элемент yield
превращает функцию в генератор. Генератор ведет себя так же, как функция, пока что-то не «поддается». Генератор останавливается до следующего вызова и продолжает работу с той же точки, с которой он был запущен. Вы можете получить последовательность всех «полученных» значений в одном, вызвав list (generator ())
.
Ключевое слово yield
просто собирает возвращаемые результаты. Думайте о yield
как return + =
Вот простой подход, основанный на yield
, для вычисления ряда Фибоначчи:
def fib(limit=50):
a, b = 0, 1
for i in range(limit):
yield b
a, b = b, a+b
Когда вы введете это в свой REPL, а затем попытаетесь позвонить, вы получите загадочный результат:
>>> fib()
<generator object fib at 0x7fa38394e3b8>
Это потому, что наличие yield
сообщило Python о том, что вы хотите создать генератор , то есть объект, который генерирует значения по требованию.
Итак, как вы генерируете эти значения? Это можно сделать либо напрямую, используя встроенную функцию next
, либо косвенно, передав его в конструкцию, которая потребляет значения. Р>
Используя встроенную функцию next ()
, вы напрямую вызываете .next
/ __ next __
, заставляя генератор вывести значение: р>
>>> g = fib()
>>> next(g)
1
>>> next(g)
1
>>> next(g)
2
>>> next(g)
3
>>> next(g)
5
Косвенно, если вы передаете fib
в цикл for
, инициализатор list
, инициализатор tuple
, или что-либо еще, что ожидает объект, который генерирует / производит значения, вы будете "потреблять" генератор до тех пор, пока он не сможет произвести больше значений (и он вернется):
results = []
for i in fib(30): # consumes fib
results.append(i)
# can also be accomplished with
results = list(fib(30)) # consumes fib
Аналогично, с инициализатором tuple
:
>>> tuple(fib(5)) # consumes fib
(1, 1, 2, 3, 5)
Генератор отличается от функции в том смысле, что он ленив. Это достигается путем поддержания локального состояния и возобновления работы в любое время. Р>
Когда вы впервые вызываете fib
, вызывая его:
f = fib()
Python компилирует функцию, встречает ключевое слово yield
и просто возвращает объект генератора обратно к вам. Не очень полезно, кажется. Р>
Когда вы запрашиваете, он генерирует первое значение, прямо или косвенно, он выполняет все найденные операторы, пока не встретит yield
, а затем вернет значение, которое вы указали для yield
и паузы. Для примера, который лучше демонстрирует это, давайте использовать некоторые вызовы print
(замените на print " text "
, если на Python 2):
def yielder(value):
""" This is an infinite generator. Only use next on it """
while 1:
print("I'm going to generate the value for you")
print("Then I'll pause for a while")
yield value
print("Let's go through it again.")
Теперь введите в REPL:
>>> gen = yielder("Hello, yield!")
у вас есть объект генератора, ожидающий команды для его создания значения. Используйте next
и посмотрите, что напечатано:
>>> next(gen) # runs until it finds a yield
I'm going to generate the value for you
Then I'll pause for a while
'Hello, yield!'
Результаты без кавычек - это то, что напечатано. Результатом в кавычках является то, что возвращается из yield
. Звоните далее
еще раз сейчас:
>>> next(gen) # continues from yield and runs again
Let's go through it again.
I'm going to generate the value for you
Then I'll pause for a while
'Hello, yield!'
Генератор запоминает, что он был приостановлен на yield value
, и возобновляет работу оттуда. Следующее сообщение печатается, и поиск оператора yield
, чтобы приостановить его, выполняется снова (из-за цикла while
).
Простой пример, чтобы понять, что это такое: yield
def f123():
for _ in range(4):
yield 1
yield 2
for i in f123():
print i
Вывод:
1 2 1 2 1 2 1 2