“yield”关键字有什么作用?

yieldPython中关键字的用途是什么?它有什么作用?

例如,我试图理解这段代码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.代码来自Jochen Schulz(jrschulz),他为度量空间创建了一个很棒的Python库.这是完整源代码的链接:模块mspace.

回答

要了解yield它的作用,您必须了解生成器是什么.而在此之前的发电机来iterables.

Iterables

创建列表时,您可以逐个阅读其项目.逐个读取它的项称为迭代:

>>> 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)
1
4

你可以使用的所有内容都是可for... in...迭代的; lists,strings,文件...

这些迭代很方便,因为您可以根据需要阅读它们,但是您将所有值存储在内存中,当您拥有大量值时,这并不总是您想要的.

发电机

生成器是迭代器,是一种只能迭代一次的迭代器.生成器不会将所有值存储在内存中,它们会动态生成值:

>>> mygenerator = (x*x for x in range(3))
>>> for i in mygenerator:
...    print(i)
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)
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

此代码包含几个智能部分:

通常我们将列表传递给它:

>>> a = [1, 2]
>>> b = [3, 4]
>>> a.extend(b)
>>> print(a)
[1, 2, 3, 4]

但是在你的代码中它得到了一个生成器,这很好,因为:

  1. 您不需要两次读取值.
  2. 您可能有很多孩子,并且您不希望它们都存储在内存中.

它的工作原理是因为Python不关心方法的参数是否是列表.Python期望iterables所以它将适用于字符串,列表,元组和生成器!这叫做鸭子打字,这也是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模块包含操作iterables的特殊函数.曾经希望复制一台发电机?链两个发电机?使用单行分组嵌套列表中的值?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__()方法)的过程.Iterables是您可以从中获取迭代器的任何对象.迭代器是允许您迭代迭代的对象.

在这篇文章中有关于for循环如何工作的更多信息.

  • 这个答案表明,'yield`不是那么神奇.当你在任何地方调用包含`yield`语句的函数时,你会得到一个生成器对象,但是没有代码运行.然后每次从生成器中提取对象时,Python都会在函数中执行代码,直到出现`yield`语句,然后暂停并传递对象.当你提取另一个对象时,Python会在`yield`之后继续,直到它到达另一个`yield`(通常是同一个,但稍后再迭代一次).这一直持续到功能结束,此时发电机被认为已耗尽.
  • **所有**迭代器只能迭代一次,而不仅仅是生成器函数生成的迭代器.如果你不相信我,在任何可迭代对象上调用`iter()`并尝试不止一次迭代结果.
  • @Craicerjack你的条款混乱了.迭代是一种带有__iter__`方法的东西.迭代器是在iterable上调用`iter()`的结果.迭代器只能迭代一次.
  • @MatthiasFripp "This continues until the function runs off the end" -- or it encounters a `return` statement. (`return` is permitted in a function containing `yield`, provided that it does not specify a return value.)
  • "这些迭代很方便......但是你把所有的值存储在内存中,这并不总是你想要的",或者错误或令人困惑.迭代器在迭代器上调用iter()时返回迭代器,并且迭代器并不总是必须将其值存储在内存中,这取决于__iter__方法的实现,它还可以按需生成序列中的值.
  • It would be nice to add to this **great** answer why *It is just the same except you used `()` instead of `[]`*, specifically what `()` is (there may be confusion with a tuple).
  • The yield statement suspends function’s execution and sends a value back to the caller, but retains enough state to enable function to resume where it is left off. When resumed, the function continues execution immediately after the last yield run. This allows its code to produce a series of values over time, rather than computing them at once and sending them back like a list.

快捷方式所著的Grokking yield

当你看到一个带yield语句的函数时,应用这个简单的技巧来理解会发生什么:

  1. result = []在函数的开头插入一行.
  2. 替换每个yield exprresult.append(expr).
  3. return result在函数底部插入一行.
  4. 耶 - 没有更多的yield陈述!阅读并找出代码.
  5. 将功能与原始定义进行比较

这个技巧可以让你了解函数背后的逻辑,但实际发生的情况与yield基于列表的方法中发生的情况明显不同.在许多情况下,yield方法将更高效,更快.在其他情况下,即使原始函数工作得很好,这个技巧也会让你陷入无限循环.请继续阅读以了解更多信息...

不要混淆您的Iterables,Iterators和Generators

首先,迭代器协议 - 当你写

for x in mylist:
...loop body...

Python执行以下两个步骤:

  1. 调用iter(mylist)- >这会返回一个带有next()方法的对象(或者__next__()在Python 3中).

    [这是大多数人忘记告诉你的步骤]

  2. 继续调用next()从步骤1返回的迭代器上的方法.返回值next()赋值给,x并执行循环体.如果StopIteration从内部引发异常next(),则意味着迭代器中没有更多值,并且退出循环.

事实上,Python在任何时候想要循环对象的内容时执行上述两个步骤- 所以它可以是for循环,但它也可以是代码otherlist.extend(mylist)(在哪里otherlist是Python列表).

mylist是一个可迭代的,因为它实现了迭代器协议.在用户定义的类中,您可以实现该__iter__()方法以使您的类的实例可迭代.此方法应返回迭代器.迭代器是一个带next()方法的对象.它可以同时实现__iter__(),并next()在同一类,并有__iter__()回报self.这适用于简单的情况,但是当您希望两个迭代器同时循环遍历同一个对象时.

所以这是迭代器协议,许多对象实现了这个协议:

  1. 内置列表,词典,元组,集,文件.
  2. 用户定义的实现类__iter__().
  3. 发电机.

请注意,for循环不知道它正在处理什么类型的对象 - 它只是遵循迭代器协议,并且很乐意在它调用时获得项目next().内置列表一个接一个地返回它们的项目,字典逐个返回,文件一个接一个地返回等.然后生成器返回......那就是yield进来的地方:

def f123():
yield 1
yield 2
yield 3
for item in f123():
print item

而不是yield语句,如果只有第一个return语句中有三个语句f123()将被执行,函数将退出.但是f123()没有普通的功能.当f123()被调用时,它返回任何值在yield语句!它返回一个生成器对象.此外,该功能并没有真正退出 - 它进入暂停状态.当for循环尝试遍历生成器对象时,该函数从yield之前返回的最后一行恢复其挂起状态,执行下一行代码,在本例中为一个yield语句,并将其作为下一项返回.这种情况一直发生,直到函数退出,此时发生器上升StopIteration,循环退出.

因此,生成器对象有点像适配器 - 在一端它展示了迭代器协议,通过公开__iter__()next()方法来保持for循环满意.然而,在另一端,它运行该功能足以从中获取下一个值,并将其重新置于挂起模式.

为什么要使用发电机?

通常,您可以编写不使用生成器但实现相同逻辑的代码.一种选择是使用我之前提到的临时列表'技巧'.这在所有情况下都不起作用,例如,如果你有无限循环,或者当你有一个很长的列表时,它可能会使内存的使用效率低下.另一种方法是实现一个新的可迭代类SomethingIter,它将状态保存在实例成员中,并在其中next()(或__next__()在Python 3中)方法中执行下一个逻辑步骤.根据逻辑,next()方法内部的代码可能看起来非常复杂并且容易出错.这里的发电机提供了一个简洁的解决方案

  • *"当你看到一个带有yield语句的函数时,应用这个简单的技巧来理解会发生什么"*这不完全忽略了你可以`发送'到生成器这一事实,这是生成器的一个重要部分?
  • "它可能是一个for循环,但它也可能是像`otherlist.extend(mylist)`"这样的代码 - >这是不正确的.`extend()`就地修改列表而不返回iterable.试图循环`otherlist.extend(mylist)`将失败并出现`TypeError`,因为`extend()`隐式返回`None`,你不能遍历`None`.
  • @pedro 你误解了这句话。这意味着python在执行`otherlist.extend(mylist)`时在`mylist`(而不是`otherlist`)上执行上述两个步骤。

想一想:

对于具有next()方法的对象,迭代器只是一个奇特的声音术语.因此,屈服函数最终会像这样:

原始版本:

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

为了更深入地了解幕后发生的事情,next()可以将循环重写为:

iterator = some_func()
try:
while 1:
print iterator.next()
except StopIteration:
pass

这更有意义还是让你更加迷惑?:)

我要指出,这为了说明的目的过于简单化.:)

  • 我在Python 3.6中尝试了这个例子,如果我创建`iterator = some_function()`,变量`iterator`就不再有一个名为`next()`的函数,而只是一个`__next __()`函数.以为我会提到它.

yield关键字简化为两个简单的事实:

  1. 如果编译器在函数内的任何位置检测到yield关键字,则该函数不再通过该语句返回.相反,它会立即返回一个称为生成器的惰性"挂起列表"对象return
  2. 生成器是可迭代的.什么是可迭代的?这就像一个东西listsetrange或字典视图,具有内置的协议以某种顺序访问每一个元素.

简而言之:生成器是一个惰性的,递增挂起的列表,并且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像Python一样的函数range.呼叫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迭代协议"的工作原理.也就是说,当你这样做时会发生什么list(makeRange(5)).这就是我之前描述的"懒惰的增量列表".

>>> x=iter(range(5))
>>> next(x)
>>> 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()函数(以及迭代协议的其他部分)来实现奇特的东西,通常以牺牲可读性为代价,因此尽量避免这样做......


细节

通常情况下,大多数人不会关心以下区别,可能想在这里停止阅读.

在Python语言中,iterable是任何"理解for循环的概念"的对象,如列表[1,2,3],迭代器是所请求的for循环的特定实例[1,2,3].__iter__().甲发生器是完全一样的任何迭代器,除了它是写(带有功能语法)的方式.

当您从列表中请求迭代器时,它会创建一个新的迭代器.但是,当您从迭代器(您很少这样做)请求迭代器时,它只会为您提供自身的副本.

因此,万一你没有做到这样的事情......

> x = myRange(5)
> list(x)
[0, 1, 2, 3, 4]
> list(x)
[]

...然后记住发电机是一个迭代器 ; 也就是说,它是一次性的.如果要重复使用它,则应myRange(...)再次调用.如果需要使用结果两次,请将结果转换为列表并将其存储在变量中x = list(myRange(5)).那些绝对需要克隆生成器的人(例如,谁正在做可怕的hackish元编程)可以使用,itertools.tee如果绝对必要,因为可复制的迭代器Python PEP标准提案已被推迟.


回答大纲/摘要

  • yield调用时,函数返回一个Generator.
  • 生成器是迭代器,因为它们实现了迭代器协议,因此您可以迭代它们.
  • 还可以生成器发送信息,使其在概念上成为协程.
  • 在Python 3中,您可以使用两个方向从一个生成器委派给另一个生成器yield from.
  • (附录批评了几个答案,包括最重要的答案,并讨论了return在发电机中的使用.)

发电机:

yield在函数定义中只是合法的,并且函数定义中包含yield它使它返回一个生成器.

生成器的想法来自其他语言(见脚注1),具有不同的实现.在Python的Generators中,代码的执行在yield的时候被冻结.当调用生成器时(下面讨论方法),执行重新开始,然后在下一个yield时冻结.

yield提供了一种实现迭代器协议的简单方法,该协议由以下两种方法定义:
__iter__next(Python 2)或__next__(Python 3).这两种方法都使对象成为迭代器,您可以使用模块中的IteratorAbstract Base Class进行类型检查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 fromPython 3中提供的内容.这允许我们将协同程序委托给子协会:

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在PEP 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做了很多.我相信我可以为此添加更全面的例子.如果您想要更多或有一些建设性的批评,请通过下面的评论告诉我.


附录:

批评最高/已接受的答案**

  • 关于什么使可迭代成为混乱,仅使用列表作为示例.请参阅上面的参考资料,但总结一下:iterable有一个__iter__返回迭代器的方法.一个迭代器提供了一个.next(Python 2里或.__next__(Python 3的)方法,它是隐式由称为for循环,直到它提出StopIteration,并且一旦这样做,将继续这样做.
  • 然后它使用生成器表达式来描述生成器是什么.因为生成器只是创建迭代器的一种方便方法,所以它只会混淆事情,我们还没有完成这个yield部分.
  • 控制生成器耗尽时,他调用该.next方法,而不是他应该使用内置函数,next.这将是一个适当的间接层,因为他的代码在Python 3中不起作用.
  • Itertools?这与什么yield都没有关系.
  • 没有讨论在Python 3 中yield提供的新功能的方法.顶部/接受的答案是一个非常不完整的答案.yield from

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核心开发人员正在讨论弃用其配额.这是邮件列表中的相关帖子:

我的投票是它是一个SyntaxError,因为你没有得到你对语法的期望.

我同意这对我们来说是一个明智的地方,因为任何依赖当前行为的代码实在太聪明而无法维护.

在达到目标方面,我们可能希望:

  • 3.7中的SyntaxWarning或DeprecationWarning
  • 2.7.x中的Py3k警告
  • 3.8中的SyntaxError

干杯,尼克.

- Nick Coghlan | ncoghlan at gmail.com | 澳大利亚布里斯班

此外,还有一个突出的问题(10544)似乎指向了这个永远不是一个好主意的方向(PyPy,一个用Python编写的Python实现,已经提出了语法警告.)

最重要的是,直到CPython的开发人员告诉我们:不要投入yield生成器表达或理解.

return发电机中的声明

在Python 2中:

An expression_list基本上是用逗号分隔的任意数量的表达式 - 实际上,在Python 2中,您可以使用停止生成器return,但不能返回值.

在Python 3中:

脚注


yield就像return- 它返回你告诉它的任何东西(作为一个发生器).区别在于下次调用生成器时,执行从最后一次调用yield语句开始.与返回不同,当产生收益时,不会清除堆栈帧,但是控制将被传回给调用者,因此其状态将在下次调用函数时恢复.

对于代码,该函数get_child_candidates的作用类似于迭代器,因此当您扩展列表时,它会一次向新列表添加一个元素.

list.extend调用迭代器直到它耗尽.对于您发布的代码示例,只返回一个元组并将其附加到列表中将更加清晰.

  • 这很接近,但不正确.每次调用带有yield语句的函数时,它都会返回一个全新的生成器对象.只有在调用该生成器的.next()方法时,才会在最后一次生成后恢复执行.

还有一件事需要提及:一个实际上不必终止收益的函数.我编写了这样的代码:

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

TL; DR

而不是这个:

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)
...
1
4
9
>>> for square in square_yield(4):
...     print(square)
...
1
4
9

不同的行为:

收益是单程:你只能迭代一次.当函数有一个yield时,我们将其称为生成函数.和迭代器是它返回.这些条款很有启发性.我们失去了容器的便利性,但是获得了根据需要计算的系列的能力,并且任意长.

收益是懒惰的,它推迟了计算.当你调用它时,一个带有yield的函数实际上根本不会执行.它返回一个迭代器对象,它记住它停止的位置.每次调用next()迭代器(这发生在for循环中)执行时,前进到下一个yield.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))
...
1
4
9

如果你需要多次通过并且系列不是太长,只需要打电话list():

>>> list(square_yield(4))
[0, 1, 4, 9]

yield因为这两个含义都适用,所以这个词的选择很棒:

...提供系列中的下一个数据.

...放弃CPU执行直到迭代器前进.


产量为您提供发电机.

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个元素的列表来说,这不是什么大问题,但是如果你想要一个500万的列表怎么办?这不仅是一个巨大的内存消耗者,而且在调用函数时也需要花费大量时间来构建.在第二种情况下,bar只给你一个发电机.生成器是可迭代的 - 这意味着您可以在for循环等中使用它,但每个值只能被访问一次.所有值也不会同时存储在内存中; 生成器对象"记住"上次调用它时循环的位置 - 这样,如果你使用一个可迭代(比方说)计数到500亿,你就不必数到500亿全部立即存储500亿个数字.同样,这是一个非常人为的例子,如果你真的想要数到500亿,你可能会使用itertools.:)

这是生成器最简单的用例.正如你所说,它可以用来编写有效的排列,使用yield来通过调用堆栈推送,而不是使用某种堆栈变量.生成器也可以用于专门的树遍历,以及其他各种方式.

  • 只是一个说明 - 在 Python 3 中,`range` 也返回一个生成器而不是一个列表,所以你也会看到类似的想法,除了 `__repr__`/`__str__` 被覆盖以显示更好的结果,在这种情况下`范围(1, 10, 2)`。

它正在返回一台发电机.我对Python并不是特别熟悉,但我相信它与C#的迭代器块相同,如果你熟悉它们的话.

关键的想法是编译器/解释器/无论做什么都有一些技巧,所以就调用者而言,他们可以继续调用next()并且它将保持返回值 - 就好像生成器方法被暂停一样.现在显然你不能真正"暂停"一个方法,所以编译器会建立一个状态机,让你记住你当前的位置以及局部变量等.这比自己编写迭代器容易得多.


在描述如何使用发电机的许多重要答案中,有一种我认为尚未给出的答案.这是编程语言理论的答案:

yieldPython中的语句返回一个生成器.Python中的生成器是一个返回continuation的函数(特别是一种coroutine,但continuation代表了理解正在发生的事情的更通用的机制).

编程语言理论的延续是一种更为基础的计算,但它们并不经常使用,因为它们极难推理并且也很难实现.但是延续的概念是直截了当的:它是尚未完成的计算状态.在此状态下,将保存变量的当前值,尚未执行的操作等.然后在程序的某个时刻,可以调用continuation,以便程序的变量重置为该状态,并执行保存的操作.

以这种更一般的形式,可以以两种方式实现继续.在call/cc方式,程序的堆栈字面上保存,然后调用延续时,堆栈恢复.

在连续传递样式(CPS)中,continuation只是普通函数(仅在函数是第一类的语言中),程序员明确地管理它并传递给子例程.在这种风格中,程序状态由闭包(以及碰巧在其中编码的变量)表示,而不是驻留在堆栈中某处的变量.管理控制流的函数接受继续作为参数(在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)

在这个(非常简单的)示例中,程序员将实际写入文件的操作保存到一个延续中(这可能是一个非常复杂的操作,需要写出许多细节),然后传递该延续(即,作为第一个 - class closure)到另一个执行更多处理的运算符,然后在必要时调用它.(我在实际的GUI编程中经常使用这种设计模式,因为它节省了我的代码行,或者更重要的是,在GUI事件触发后管理控制流.)

在不失一般性的情况下,本文的其余部分将继续概念化为CPS,因为它更容易理解和阅读.

现在让我们来谈谈Python中的生成器.生成器是延续的特定子类型.虽然continuation通常能够保存计算的状态(即程序的调用堆栈),但生成器只能通过迭代器保存迭代状态.虽然这个定义对于某些发电机的使用情况略有误导.例如:

def f():
while True:
yield 4

这显然是一个合理的迭代,其行为很明确 - 每次生成器迭代它,它返回4(并且永远这样做).但是,在考虑迭代器(即,for x in collection: do_something(x))时,它可能不是想到的典型迭代类型.这个例子说明了生成器的强大功能:如果有任何东西是迭代器,生成器可以保存它的迭代状态.

重申:Continuations可以保存程序堆栈的状态,生成器可以保存迭代状态.这意味着continuation比生成器更强大,但生成器也很多,更容易.它们对于语言设计者来说更容易实现,并且程序员更容易使用它们(如果你有时间刻录,尝试阅读和理解这个页面有关continuation和call/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生成器函数,即包含a的函数yield.
  • 过了一会儿,我告诉你,"好的,准备告诉我数字的顺序".
    此步骤对应于调用返回生成器对象的生成器函数.请注意,你还没有告诉我任何数字; 你抓住你的纸和铅笔.
  • 我问你,"告诉我下一个号码",你告诉我第一个号码; 之后,你等我问你下一个号码.这是你的工作,要记住你在哪里,你已经说过什么数字,以及下一个数字是什么.我不关心细节.
    此步骤对应于调用.next()生成器对象.
  • ...重复上一步,直到......
  • 最终,你可能会走到尽头.你没告诉我一个号码; 你只是喊道,"抓住你的马!我已经完成了!没有更多的数字!"
    此步骤对应于生成器对象结束其作业,并引发StopIteration异常生成器函数不需要引发异常.当函数结束或发出时,它会自动引发return.

这就是生成器的作用(包含a的函数yield); 它开始执行,只要它执行一次就暂停yield,当被要求输入一个.next()值时,它会从最后一次继续执行.它完全符合Python的迭代器协议的设计,它描述了如何顺序请求值.

迭代器协议最着名的用户是forPython中的命令.所以,每当你做一个:

for item in sequence:

如果sequence是如上所述的列表,字符串,字典或生成器对象并不重要; 结果是一样的:你逐个读取序列中的项目.

请注意,def包含yield关键字的函数不是创建生成器的唯一方法; 这只是创建一个最简单的方法.

有关更准确的信息,请阅读Python文档中的迭代器类型,yield语句和生成器.


虽然很多答案都说明了为什么要使用a yield创建生成器,但有更多的用途yield.制作协程非常容易,它可以在两个代码块之间传递信息.我不会重复任何已经给出的关于使用yield创建生成器的精细示例.

为了帮助理解yield以下代码中的功能,您可以用手指在任何具有代码的代码中跟踪循环yield.每当你的手指击中时yield,你必须等待a next或a send进入.当a next被调用时,你会遍历代码,直到你点击yield... yield评估右侧的代码并返回给调用者...然后你等待.当next被再次调用,您通过代码进行另一次循环.但是,您会注意到在协程中,yield也可以使用send...,它将从调用者发送一个值屈服函数.如果send给出a,则yield接收发送的值,并将其从左侧吐出...然后通过代码的跟踪进行直到yield再次击中(在结束时返回值,就像next被调用一样).

例如:

>>> def coroutine():
...     i = -1
...     while True:
...         i += 1
...         val = (yield i)
...         print("Received %s" % val)
...
>>> sequence = coroutine()
>>> sequence.next()
>>> sequence.next()
Received None
1
>>> sequence.send('hello')
Received hello
2
>>> sequence.close()
  • 可爱的![蹦床](https://en.wikipedia.org/wiki/Trampoline_(computing))(在 Lisp 意义上)。很少有人看到这些!

还有另一种yield用途和含义(自Python 3.3起):

yield from <expr>

PEP 380 - 委托给子发电机的语法:

当一个生成器重新生成另一个生成器生成的值时,新语法也会为优化提供一些机会.

此外,这将介绍(自Python 3.5):

async def new_coroutine(data):
...
await blocking_action()

避免协程与常规发生器混淆(今天yield两者都使用).


所有伟大的答案,但新手有点困难.

我假设你已经学会了这个return陈述.

作为类比,return并且yield是双胞胎.return意思是"回归和停止",而"产量"意味着"回归,但继续"

  1. 尝试获取num_list return.
def num_list(n):
for i in range(n):
return i

运行:

In [5]: num_list(3)
Out[5]: 0

看,你只得到一个数字而不是它们的列表.return永远不会让你高兴,只执行一次并退出.

  1. 来了 yield

替换returnyield:

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运行时间计划.你可以理解returnreturn one of them,和yield作为return all of them.这叫做iterable.

  1. 我们可以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是双胞胎
  • 这是可以理解的,但一个主要区别是您可以在一个函数/方法中拥有多个收益。类比在这一点上完全失效。Yield 会记住它在函数中的位置,所以下次你调用 next() 时,你的函数会继续执行下一个 `yield`。我认为这很重要,并且应该表达出来。

下面是一些如何实际实现生成器的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)

我将发布"阅读Beazley的'Python:基本参考'第19页以快速描述发生器",但是很多其他人已经发布了很好的描述.

另外,请注意,yield可以在协同程序中使用它们作为它们在生成器函数中的双重使用.虽然它与您的代码片段的用法不同,(yield)但可以用作函数中的表达式.当调用者使用该send()方法向方法发送值时,协程将执行,直到(yield)遇到下一个语句.

生成器和协同程序是设置数据流类型应用程序的一种很酷的方法.我认为yield在函数中知道语句的其他用法是值得的.


从编程的角度来看,迭代器实现为thunk.

为了实现并发执行的迭代器,生成器和线程池等作为thunks(也称为匿名函数),使用发送给具有调度程序的闭包对象的消息,并且调度程序回答"消息".

http://en.wikipedia.org/wiki/Message_passing

" next "是发送到闭包的消息,由" iter "调用创建.

有很多方法可以实现这个计算.我使用了变异,但通过返回当前值和下一个yielder,很容易做到没有变异.

这是一个使用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保持程序流的位置和下一个循环从"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_codegi_frame字段是冻结状态的存储位置.通过探索它们dir(..),我们可以确认我们的上述心理模型是可信的.


像每个答案所暗示的那样,yield用于创建序列生成器.它用于动态生成一些序列.例如,在网络上逐行读取文件时,可以使用以下yield函数:

def getNextLines():
while con.isOpen():
yield con.read()

您可以在代码中使用它,如下所示:

for line in getNextLines():
doSomeThing(line)

执行控制转移问题

执行foryield时,执行控件将从getNextLines()传送到循环.因此,每次调用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"

(我的下面的回答只是从使用Python生成器的角度讲,而不是生成器机制的底层实现,它涉及堆栈和堆操作的一些技巧.)

当在python函数中yield使用when 而不是a return时,该函数被转换为一些特殊的函数generator function.该函数将返回一个generator类型的对象.yield关键字是一个标志,通知蟒蛇编译器将特殊对待这样的功能.一旦从其返回某个值,正常函数将终止.但是在编译器的帮助下,生成器函数可以被认为是可恢复的.也就是说,将恢复执行上下文,并且执行将从上次运行继续.直到你显式调用return,这将引发StopIteration异常(也是迭代器协议的一部分),或者到达函数的末尾.我发现了很多关于引用的generator,但这一个从functional programming perspective最消化的.

(现在我想谈谈背后的基本原理generator,并iterator基于我自己的理解.我希望这可以帮助你掌握迭代器和生成器的基本动机.这样的概念也出现在其他语言中,比如C#.)

据我了解,当我们想要处理大量数据时,我们通常首先将数据存储在某处,然后逐个处理.但这种天真的方法是有问题的.如果数据量很大,那么事先将它们作为一个整体存储起来是很昂贵的.因此,不是data直接存储自身,为什么不metadata间接存储某种,即the logic how the data is computed.

有两种方法来包装这样的元数据.

  1. OO方法,我们包装元数据as a class.这就是所谓的iterator实现迭代器协议(即__next__(),和__iter__()方法)的人.这也是常见的迭代器设计模式.
  2. 功能方法,我们包装元数据as a function.这就是所谓的generator function.但在引擎盖下,返回的generator object仍然是IS-A迭代器,因为它还实现了迭代器协议.

无论哪种方式,都会创建一个迭代器,即一些可以为您提供所需数据的对象.OO方法可能有点复杂.无论如何,使用哪一个取决于你.


总之,该yield语句将您的函数转换为一个工厂,该工厂生成一个名为a的特殊对象,该对象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

但这是低效的,因为

  • 您创建一个只使用一次的数组(这会浪费内存)
  • 这段代码实际上循环遍历该数组两次!:(

幸运的是,Guido和他的团队足够慷慨地开发发电机,所以我们可以做到这一点;

def myRangeSmart(i):
n = 0
while n < i:
yield n
n = n + 1
return
for i in myRangeSmart(10):
print i

现在,在每次迭代时,调用的生成器上next()的函数执行函数,直到它达到'yield'语句,在该语句中它停止并"产生"该值或到达函数的末尾.在这种情况下,在第一次调用时,next()执行yield语句并生成'n',在下一次调用时它将执行increment语句,跳回'while',计算它,如果为true,它将停止并且再次屈服'n',它将继续这样,直到while条件返回false并且生成器跳转到函数的末尾.


收益率是一个对象

return函数中的A 将返回单个值.

如果您希望函数返回一组大量值,请使用yield.

更重要的yield是,是一个障碍.

也就是说,它将从头开始运行代码,直到它命中yield.然后,它将返回循环的第一个值.

然后,每隔一个调用将再次运行您在函数中写入的循环,返回下一个值,直到没有任何值返回.


许多人使用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

def f123():
for _ in range(4):
yield 1
yield 2
for i in f123():
print i

输出是:

1 2 1 2 1 2 1 2
  • are you sure about that output? wouldnt that only be printed on a single line if you ran that print statement using `print(i, end=' ')`? Otherwise, i believe the default behavior would put each number on a new line

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您希望创建一个生成器,即一个按需生成值的对象.

那么,你如何生成这些值?这可以通过使用内置函数直接完成,也可以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关键字并简单地返回一个生成器对象.看起来不是很有帮助.

然后,当您请求它直接或间接生成第一个值时,它会执行它找到的所有语句,直到遇到a yield,然后它会返回您提供给的值yield并暂停.有一个更好地演示这个的例子,让我们使用一些print调用(print "text"在Python 2上用if 替换):

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现在再打电话:

>>> 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循环).


另一个TL; DR

列表上的迭代器:next()返回列表的下一个元素

迭代器生成器:next()将动态计算下一个元素(执行代码)

您可以看到yield/generator作为一种从外部手动运行控制流的方法(如继续循环一步),通过调用next,无论流程如何复杂.

注意:生成器不是正常功能.它会记住以前的状态,如局部变量(堆栈).有关详细说明,请参阅其他答案或文章.生成器只能迭代一次.你可以没有yield,但它不会那么好,所以它可以被认为是'非常好'的语言糖.


收益率收益率相似.不同之处是:

yield使函数可迭代(在下面的示例中,primes(n = 1)函数变为可迭代).
它本质上意味着下次调用该函数时,它将从它离开的位置继续(在该行之后yield expression).

def isprime(n):
if n == 1:
return False
for x in range(2, n):
if n % x == 0:
return False
else:
return True
def primes(n = 1):
while(True):
if isprime(n): yield n
n += 1
for n in primes():
if n > 100: break
print(n)

在上面的例子中,如果isprime(n)为真,它将返回素数.在下一次迭代中,它将从下一行继续

n += 1

打个比方可能有助于在这里理解这个想法:

想象一下,您创造了一台惊人的机器,每天能够产生成千上万个灯泡。机器会在具有唯一序列号的盒子中生成这些灯泡。您没有足够的空间来同时存储所有这些灯泡(即,由于存储限制,您无法跟上机器的速度),因此您希望调整此机器以根据需要生成灯泡。

Python生成器与此概念没有太大区别。

想象一下,您有一个x为盒子生成唯一序列号的函数。显然,您可以通过函数生成大量此类条形码。一个更明智,更节省空间的选择是按需生成这些序列号。

机器代码:

def barcode_generator():
serial_number = 10000  # Initial barcode
while True:
yield serial_number
serial_number += 1
barcode = barcode_generator()
while True:
number_of_lightbulbs_to_generate = int(input("How many lightbulbs to generate? "))
barcodes = [next(barcode) for _ in range(number_of_lightbulbs_to_generate)]
print(barcodes)
# function_to_create_the_next_batch_of_lightbulbs(barcodes)
produce_more = input("Produce more? [Y/n]: ")
if produce_more == "n":
break

如您所见,我们有一个独立的“功能”,可以每次生成下一个唯一的序列号。该函数返回一个生成器!如您所见,我们并不是在每次需要新的序列号时都调用该函数,而是使用next()给定的生成器来获取下一个序列号。

输出:

How many lightbulbs to generate? 5
[10000, 10001, 10002, 10003, 10004]
Produce more? [Y/n]: y
How many lightbulbs to generate? 6
[10005, 10006, 10007, 10008, 10009, 10010]
Produce more? [Y/n]: y
How many lightbulbs to generate? 7
[10011, 10012, 10013, 10014, 10015, 10016, 10017]
Produce more? [Y/n]: n

还可以将数据发送回生成器!

事实上,正如这里的许多答案所解释的那样,使用yield会创建一个generator.

您可以使用yield关键字将数据发送回“实时”生成器

例子:

假设我们有一种将英语翻译成其他语言的方法。而且在它开始时,它会做一些很重的事情,应该做一次。我们希望这个方法永远运行(不知道为什么.. :)),并接收要翻译的单词。

def translator():
# load all the words in English language and the translation to 'other lang'
my_words_dict = {'hello': 'hello in other language', 'dog': 'dog in other language'}
while True:
word = (yield)
yield my_words_dict.get(word, 'Unknown word...')

跑步:

my_words_translator = translator()
next(my_words_translator)
print(my_words_translator.send('dog'))
next(my_words_translator)
print(my_words_translator.send('cat'))

将打印:

dog in other language
Unknown word...

总结一下:

使用send生成器内部的方法将数据发送回生成器。为此,使用了 a (yield)


这里的所有答案都很棒; 但只有其中一个(投票最多的一个)与您的代码的工作方式有关.其他人与发电机有关,以及它们如何工作.

所以我不会重复发电机是什么或产量是多少; 我认为这些都是现有的答案.然而,在花了几个小时试图理解你的类似代码之后,我会分解它是如何工作的.

您的代码遍历二叉树结构.我们以这棵树为例:

    5
/ \
3   6
/ \   \
1   4   8

另一个更简单的二叉搜索树遍历实现:

class Node(object):
..
def __iter__(self):
if self.has_left_child():
for child in self.left:
yield child
yield self.val
if self.has_right_child():
for child in self.right:
yield child

执行代码在Tree对象上,实现__iter__如下:

def __iter__(self):
class EmptyIter():
def next(self):
raise StopIteration
if self.root:
return self.root.__iter__()
return EmptyIter()

while candidates声明可以替换for element in tree; Python将此翻译为

it = iter(TreeObj)  # returns iter(self.root) which calls self.root.__iter__()
for element in it:
.. process element ..

因为Node.__iter__函数是一个生成器,所以它内部的代码每次迭代都会执行.因此执行将如下所示:

  1. 根元素是第一个; 检查它是否已离开子for节点并迭代它们(让我们称之为it1,因为它是第一个迭代器对象)
  2. 它有一个孩子所以for执行.在for child in self.left创建一个新的迭代器self.left,这是一个节点对象本身(IT2)
  3. 与2相同的逻辑,并iterator创建一个新的(it3)
  4. 现在我们到达了树的左端.it3没有留下的孩子,所以它继续和yield self.value
  5. 在下一次调用next(it3)它时会引发StopIteration并存在,因为它没有正确的子项(它到达函数的末尾而没有产生任何东西)
  6. it1并且it2仍处于活跃状态 - 他们没有筋疲力尽,而且召唤next(it2)会产生价值而不是提高价值StopIteration
  7. 现在我们回到it2上下文,并next(it2)在它停止的地方继续调用:在yield child声明之后.由于它没有剩下的孩子,它继续并产生它self.val.

这里的问题是每次迭代都会创建遍历树的子迭代器,并保持当前迭代器的状态.一旦到达末尾,它就会遍历堆栈,并以正确的顺序返回值(最小的收益率值).

您的代码示例在不同的技术中执行了类似的操作:它为每个子项填充了一个元素列表,然后在下一次迭代时弹出它并在当前对象上运行函数代码(因此self).

我希望这对这个传奇话题有所贡献.我花了几个小时来绘制这个过程来理解它.


Python generators(一种特殊类型iterators)用于生成一系列值,yield关键字就像return生成器函数的关键字一样.

另一个有趣的yield关键字是保存state生成器功能.

因此,我们可以在number每次generator收益时设置一个不同的值.

这是一个实例:

def getPrimes(number):
while True:
if isPrime(number):
number = yield number     # a miracle occurs here
number += 1
def printSuccessivePrimes(iterations, base=10):
primeGenerator = getPrimes(base)
primeGenerator.send(None)
for power in range(iterations):
print(primeGenerator.send(base ** power))

yield产生一些东西。就像有人要您制作5个纸杯蛋糕。如果您至少完成了一个蛋糕,则可以在制作其他蛋糕时将其交给他们吃。

In [4]: def make_cake(numbers):
...:     for i in range(numbers):
...:         yield 'Cake {}'.format(i)
...:
In [5]: factory = make_cake(5)

factory就是所谓的发电机,它使您成为蛋糕。如果调用make_function,则会得到一个生成器,而不是运行该函数。这是因为当yield关键字出现在函数中时,它就变成了生成器。

In [7]: next(factory)
Out[7]: 'Cake 0'
In [8]: next(factory)
Out[8]: 'Cake 1'
In [9]: next(factory)
Out[9]: 'Cake 2'
In [10]: next(factory)
Out[10]: 'Cake 3'
In [11]: next(factory)
Out[11]: 'Cake 4'

他们消耗了所有蛋糕,但又要了一个。

In [12]: next(factory)
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-12-0f5c45da9774> in <module>
----> 1 next(factory)
StopIteration:

他们被告知不要再提出更多要求。因此,一旦消耗了生成器,就可以完成它。make_cake如果您想要更多的蛋糕,则需要再次致电。这就像下一次订购纸杯蛋糕一样。

In [13]: factory = make_cake(3)
In [14]: for cake in factory:
...:     print(cake)
...:
Cake 0
Cake 1
Cake 2

您还可以将for循环与上述生成器一并使用。

再举一个例子:假设您需要一个随机密码。

In [22]: import random
In [23]: import string
In [24]: def random_password_generator():
...:     while True:
...:         yield ''.join([random.choice(string.ascii_letters) for _ in range(8)])
...:
In [25]: rpg = random_password_generator()
In [26]: for i in range(3):
...:     print(next(rpg))
...:
FXpUBhhH
DdUDHoHn
dvtebEqG
In [27]: next(rpg)
Out[27]: 'mJbYRMNo'

rpg是一个生成器,可以生成无限数量的随机密码。因此,我们也可以说,当我们不知道序列的长度时,生成器很有用,这与list的元素数量有限不同。


Python 中的 yield 与 return 语句类似,只是有一些不同。如果必须从函数返回多个值,return 语句会将所有值作为列表返回,并且必须存储在调用者块的内存中。但是如果我们不想使用额外的内存呢?相反,我们希望在需要时从函数中获取值。这就是收益的用武之地。考虑以下函数:-

def fun():
yield 1
yield 2
yield 3

来电者是:-

def caller():
print ('First value printing')
print (fun())
print ('Second value printing')
print (fun())
print ('Third value printing')
print (fun())

调用上述代码段(调用函数)时,输出:-

First value printing
1
Second value printing
2
Third value printing
3

从上面可以看出,yield 向其调用者返回一个值,但是当再次调用该函数时,它不是从第一条语句开始,而是从紧接在 yield 之后的语句开始。在上面的例子中,打印了“第一值打印”并调用了该函数。1 被退回并打印。然后打印“第二个值打印”并再次调用 fun() 。它没有打印 1(第一条语句),而是返回 2,即紧接在 yield 1 之后的语句。进一步重复相同的过程。


产量

>>> def create_generator():
...    my_list = range(3)
...    for i in my_list:
...        yield i*i
...
>>> my_generator = create_generator() # create a generator
>>> print(my_generator) # my_generator is an object!
<generator object create_generator at 0xb7555c34>
>>> for i in my_generator:
...     print(i)
1
4

简而言之,您可以看到循环不会停止并且即使在发送对象或变量之后也会继续运行(与return循环在执行后停止的位置不同).


以上是“yield”关键字有什么作用?的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>