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


有什么用? 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.代码来自Jochen Schulz(jrschulz),他为度量空间创建了一个很棒的Python库。这是完整来源的链接: 模块mspace


8325
2017-10-23 22:21


起源




答案:


要明白什么 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)
0
1
4

你能用的一切“for... in...“on是可迭代的; listsstrings,文件......

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

发电机

生成器是迭代器,是一种可迭代的 你只能迭代一次。生成器不会将所有值存储在内存中, 他们在运行中生成值

>>> 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() method是一个列表对象方法,它需要一个iterable并将其值添加到列表中。

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

>>> 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 循环工作


12189
2017-10-23 22:48



所有 迭代器只能迭代一次,而不仅仅是生成器函数生成的迭代器。如果你不相信我,请致电 iter() 在任何可迭代对象上并尝试多次迭代结果。 - augurar
@Craicerjack你的条款混乱了。一个可迭代的东西 __iter__ 方法。迭代器是调用的结果 iter() 在一个可迭代的。迭代器只能迭代一次。 - augurar
yield 这个答案表明并不是那么神奇。当您调用包含a的函数时 yield 在任何地方声明,你得到一个生成器对象,但没有代码运行。然后,每次从生成器中提取对象时,Python都会在函数中执行代码,直到它出现 yield 声明,然后暂停并传递对象。当你提取另一个对象时,Python会在之后恢复 yield 并继续直到它到达另一个 yield (通常是同一个,但稍后再迭代一次)。这一直持续到功能结束,此时发电机被认为是耗尽的。 - Matthias Fripp
@MatthiasFripp指出了究竟是什么让我绊倒了这个答案。 “你的代码每次都会运行”是一个误导性的陈述。 “你的代码将从它停止的地方继续”是一种更准确的方式来表达它。 - Indigenuity
“这些迭代很方便......但是你把所有的值存储在内存中并且这并不总是你想要的”,或者是错误的或者是混乱的。一个iterable在调用iterable上的iter()时返回一个迭代器,而迭代器并不总是必须将其值存储在内存中,具体取决于 ITER 方法,它还可以按需生成序列中的值。 - picmate 涅


快捷方式 所著的Grokking  yield

当你看到一个功能 yield 声明,应用这个简单的技巧来了解将会发生什么:

  1. 插入一行 result = [] 在功能的开头。
  2. 替换每个 yield expr 同 result.append(expr)
  3. 插入一行 return result 在功能的底部。
  4. 耶 - 没有了 yield 声明!阅读并找出代码。
  5. 比较功能与原始定义。

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

不要混淆你的Iterables,Iterators和Generators

首先, 迭代器协议  - 写的时候

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

Python执行以下两个步骤:

  1. 获取一个迭代器 mylist

    呼叫 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 它之前返回,执行下一行代码,在本例中为a yield 声明,并将其作为下一个项目返回。这种情况一直发生,直到函数退出,此时发生器才会升起 StopIteration,循环退出。

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

为什么要使用发电机?

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


1638
2017-10-25 21:22



“当你看到一个带有yield语句的函数时,应用这个简单的技巧来理解会发生什么” 这不完全忽略了你能做到的事实 send 进入发电机,这是发电机的重要组成部分? - DanielSank
“它可能是一个for循环,但也可能是代码 otherlist.extend(mylist)“ - >这是不正确的。 extend() 就地修改列表并且不返回可迭代的列表。试图循环 otherlist.extend(mylist) 会失败的 TypeError 因为 extend() 隐含地返回 None,你不能循环 None。 - Pedro
@pedro你误解了这句话。这意味着python执行上面提到的两个步骤 mylist (不开 otherlist)执行时 otherlist.extend(mylist)。 - today


想一想:

对于具有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

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

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

这更有意义还是只是让你感到困惑? :)

我应该注意到这一点  用于说明目的的过度简化。 :)


397
2017-10-23 22:28



__getitem__ 可以定义而不是 __iter__。例如: class it: pass; it.__getitem__ = lambda self, i: i*10 if i < 10 else [][0]; for i in it(): print(i),它将打印:0,10,20,...,90 - jfs
我在Python 3.6中尝试了这个例子,如果我创建的话 iterator = some_function(),变量 iterator 没有一个叫做的函数 next() 不再只是一个 __next__() 功能。以为我会提到它。 - Peter


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

  1. 如果编译器检测到 yield 关键词 随地 在函数内部,该函数不再通过 return 声明。 代替,它 立即 返回一个 懒惰的“待定列表”对象 叫做发电机
  2. 生成器是可迭代的。什么是 迭代?这就像是一个 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 就像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),或者用 yieldwhile循环中的关键字。

请注意:生成器实际上可以用于更多的东西,例如 执行协同程序 或非确定性编程或其他优雅的东西。但是,我在这里提出的“懒惰列表”观点是您将找到的最常见的用途。


在幕后

这就是“Python迭代协议”的工作原理。也就是说,当你这样做时会发生什么 list(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() function,它是“迭代协议”的一部分,可以在所有迭代器上找到。你可以手动使用 next() 函数(和迭代协议的其他部分)实现花哨的东西,通常以牺牲可读性为代价,所以尽量避免这样做......


细节

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

在Python中说,一个 迭代 是任何“理解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 标准提案已被推迟。


348
2018-06-19 06:33





什么是 yield 关键字在Python中做什么?

回答大纲/摘要

  • 一个功能 yield,当被召唤时, 返回一个 发电机
  • 生成器是迭代器,因为它们实现了 迭代器协议,所以你可以迭代它们。
  • 发电机也可以 发送信息,从概念上讲,它是一个 协程
  • 在Python 3中,您可以 代表 从一个发电机到另一个发电机在两个方向上 yield from
  • (附录批评了几个答案,包括最重要的答案,并讨论了使用 return 在发电机中。)

发电机:

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

生成器的想法来自其他语言(见脚注1),具有不同的实现。在Python的Generators中,代码的执行是 冻结的 在收益率。当调用生成器时(下面讨论方法),执行将恢复,然后在下一次生成时冻结。

yield 提供了一个 简单的方法 实现迭代器协议,由以下两种方法定义: __iter__ 和 next (Python 2)或 __next__ (Python 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

合作代表团参与Sub-Coroutine yield from

现在,回想一下 yield from 在Python 3中可用。这允许我们委派 corcorines到subcorcorine:

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 关键字在Python中做什么?

事实证明 yield 做了很多。我相信我可以添加更多 这方面的例子。如果你想要更多或有一些建设性的批评,请通过评论告诉我 下面。


附录:

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

  • 它是什么使得混淆 迭代,仅使用列表作为示例。请参阅上面的参考资料,但总结一下:一个iterable有一个 __iter__ 返回的方法 迭代器。一个 迭代器 提供了一个 .next (Python 2或 .__next__ (Python 3)方法,由隐式调用 for循环直到它升起 StopIteration,一旦它这样做,它将继续这样做。
  • 然后它使用生成器表达式来描述生成器是什么。因为生成器只是创建一个方便的方法 迭代器,它只是混淆了这个问题,我们还没有到达 yield 部分。
  • 控制发电机的耗尽 他称之为 .next 方法,相反,他应该使用内置函数, next。这将是一个适当的间接层,因为他的代码在Python 3中不起作用。
  • Itertools?这与什么无关 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核心开发人员是 讨论弃用其津贴。 这是邮件列表中的相关帖子:

2017年1月30日19:05,Brett Cannon写道:

在Sun,2017年1月29日16:39 Craig Rodrigues写道:

无论采用哪种方法,我都行。把事情留在Python 3中       不好,恕我直言。

我的投票是它是一个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

在发电机功能中, return 声明不允许包含 expression_list。在这种情况下,一个裸露的 return 表示发电机已完成并将导致 StopIteration 待提高。

一个 expression_list 基本上是用逗号分隔的任意数量的表达式 - 基本上,在Python 2中,你可以用它来停止生成器 return,但你不能返回一个值。

Python 3

在发电机功能中, return 声明表明发生器已完成并将导致 StopIteration 待提高。返回值(如果有)用作构造的参数 StopIteration 并成为 StopIteration.value属性。

脚注

  1. 提案中引用了CLU,Sather和Icon语言 向Python介绍生成器的概念。一般的想法是 一个函数可以维持内部状态并产生中间函数 数据点由用户按需提供。这是承诺的 性能优越 其他方法,包括Python线程,某些系统甚至都没有。

  2.  这意味着,例如,那 xrange 物体(range 在Python 3)不是 Iterators,即使它们是可迭代的,因为它们可以被重用。像列表一样,他们的 __iter__ 方法返回迭代器对象。

  3. yield 最初是作为声明引入的,意思是它 只能出现在代码块中一行的开头。 现在 yield 创建一个yield表达式。 https://docs.python.org/2/reference/simple_stmts.html#grammar-token-yield_stmt  这种变化是 建议 允许用户将数据发送到生成器中 一个人可能会收到它。要发送数据,必须能够将其分配给某些内容,并且 为此,声明不会起作用。


254
2018-06-25 06:11





yield 就像 return  - 它返回你告诉它的任何东西(作为生成器)。不同的是,下次调用生成器时,执行从最后一次调用开始 yield 声明。与回归不同 当产量发生时,不会清除堆栈帧,但是控制权会转移回调用者,因此其状态将在下次恢复时恢复。

在您的代码的情况下,该功能 get_child_candidates 就像迭代器一样,当你扩展列表时,它会一次向新列表添加一个元素。

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


230
2017-10-23 22:24



这很接近,但不正确。每次调用带有yield语句的函数时,它都会返回一个全新的生成器对象。只有在调用该生成器的.next()方法时才会在最后一次生成后恢复执行。 - kurosch
似乎缺少了一些东西 “将在下次恢复功能”。应该是吗? “将在下次恢复该功能 运行“? - Peter Mortensen


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

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);

它确实有助于简化一些问题,并使一些事情更容易使用。


182
2017-10-24 08:44





对于那些喜欢最小化工作范例的人,请冥想这种互动 蟒蛇 会议:

>>> 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

155
2018-01-18 17:25



这不回答这个问题 - ppperry


产量为您提供发电机。

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来通过调用堆栈推送,而不是使用某种堆栈变量。生成器也可以用于专门的树遍历,以及其他各种方式。


133
2018-01-16 06:42





它正在返回一台发电机。我对Python并不是特别熟悉,但我相信它和它一样 C#的迭代器块 如果你熟悉那些。

有一个 IBM文章 据我所知,它解释得相当好(对于Python)。

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


125
2017-10-23 22:26





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

yield Python中的语句返回一个生成器。 Python中的生成器是一个返回的函数 延续 (特别是一种协程,但是延续代表了理解正在发生的事情的更一般的机制)。

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

以这种更一般的形式,可以以两种方式实现连续。在里面 call/cc 方式,程序的堆栈实际上是保存的,然后当调用continuation时,堆栈将被恢复。

在连续传递样式(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中的生成器。生成器是延续的特定子类型。而 延续通常能够保存a的状态 计算 (即程序的调用堆栈), 生成器只能保存迭代的状态 迭代器。虽然这个定义对于某些发电机的使用情况略有误导。例如:

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 关键词。


120
2018-04-04 14:56