python:关键字yield有什么用?

在Python中使用yield关键字有什么用?它是做什么的?

例如,我试图理解这段代码1:

1
2
3
4
5
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

这是来电者:

1
2
3
4
5
6
7
8
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库。这是到完整源代码的链接:Module mspace.


要理解yield的作用,您必须理解生成器是什么。在生成器出现之前是可迭代的。

iterable

当您创建一个列表时,您可以逐个读取它的项。逐项读取其项称为迭代:

1
2
3
4
5
6
>>> mylist = [1, 2, 3]
>>> for i in mylist:
...    print(i)
1
2
3

mylist是一个可迭代的。当你使用列表理解,你创建一个列表,因此一个迭代:

1
2
3
4
5
6
>>> mylist = [x*x for x in range(3)]
>>> for i in mylist:
...    print(i)
0
1
4

您可以在上面使用"for... in..."的所有内容都是可迭代的;lists, strings、文件……

这些迭代器非常方便,因为您可以随心所欲地读取它们,但是您将所有的值都存储在内存中,当您有很多值时,这并不总是您想要的。

<

发电机/ hh2 >

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

1
2
3
4
5
6
>>> 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类似的关键字,只是函数将返回一个生成器。

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> 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"

代码解释

发电机:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 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

打电话者:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 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()方法是一个列表对象方法,它期望一个迭代器并将其值添加到列表中。

通常我们传递一个列表给它:

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

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

您不需要读取值两次。你可能有很多孩子,你不想把他们都存储在内存中。

它之所以有效是因为Python并不关心方法的参数是否为列表。Python希望使用迭代器,所以它可以处理字符串、列表、元组和生成器!这就是所谓的duck typing,这也是Python如此酷的原因之一。但这是另一个故事,另一个问题……

你可以停在这里,或者读一点,看看发电机的高级用法:

控制生成器耗尽

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
>>> 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
...

注意:对于python3,使用print(corner_street_atm.__next__())print(next(corner_street_atm))

它可以用于控制对资源的访问等各种事情。

Itertools,你最好的朋友

itertools模块包含一些特殊的函数来操作iterables。有没有想过复制一个生成器?链两个发电机?用一行程序将嵌套列表中的值分组?Map / Zip不创建另一个列表?

import itertools

一个例子吗?让我们来看看四马比赛的可能顺序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
>>> 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循环如何工作的信息。


到Grokking yield的快捷方式

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

在函数的开头插入一行result = []。用result.append(expr)替换每个yield expr。在函数的底部插入一行return result。耶-没有更多的yield声明!阅读并找出代码。将函数与原始定义进行比较。

这个技巧可以让您了解函数背后的逻辑,但是使用yield实际发生的情况与基于列表的方法中发生的情况有很大的不同。在许多情况下,yield方法将大大提高内存效率和速度。在其他情况下,这个技巧会让您陷入无限循环,即使原始函数工作得很好。继续往下读,了解更多……

不要混淆您的迭代器、迭代器和生成器

首先,迭代器协议——当您编写时

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

Python执行以下两个步骤:

获取mylist的迭代器:

调用iter(mylist) ->这将返回一个带有next()方法的对象(或Python 3中的__next__())。

(这是大多数人忘记告诉你的一步)

使用迭代器循环项目:

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

事实上,Python在任何时候想要循环对象的内容时都会执行上面的两个步骤——所以它可以是for循环,但也可以是像otherlist.extend(mylist)这样的代码(其中otherlist是一个Python列表)。

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

这就是迭代器协议,很多对象都实现了这个协议:

内置列表,字典,元组,集,文件。实现__iter__()的用户定义类。发电机。

注意,for循环并不知道它在处理什么类型的对象——它只是遵循迭代器协议,并且很乐意在调用next()时获得一个又一个项。内置列表一个接一个地返回它们的项,字典一个接一个地返回键,文件一个接一个地返回行,等等。和发电机返回……这就是yield的作用所在:

1
2
3
4
5
6
7
def f123():
    yield 1
    yield 2
    yield 3

for item in f123():
    print item

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

因此生成器对象有点像适配器——在一端,它通过公开__iter__()next()方法来展示迭代器协议,以保持for循环的良好状态。然而,在另一端,它运行该函数的时间刚好可以获得下一个值,并将其恢复到挂起模式。

为什么使用发电机?

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


这样想:

迭代器只是一个好听的术语,用来表示具有next()方法的对象。屈服函数最终是这样的:

原始版本:

1
2
3
4
5
6
def some_function():
    for i in xrange(4):
        yield i

for i in some_function():
    print i

这基本上就是Python解释器对上面代码所做的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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循环可以重写为:

1
2
3
4
5
6
iterator = some_func()
try:
    while 1:
        print iterator.next()
except StopIteration:
    pass

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

我应该指出,出于说明的目的,这是一种过度简化。:)


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

如果编译器在函数的任何地方检测到yield关键字,该函数将不再通过return语句返回。相反,它立即返回一个称为生成器的惰性"挂起列表"对象生成器是可迭代的。什么是可迭代的?它类似于listsetrange或dict-view,具有一个内置协议,用于按特定顺序访问每个元素。

简而言之:生成器是一个延迟的、增量挂起的列表,yield语句允许您使用函数表示法来编写生成器应该增量输出的列表值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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]]

例子

让我们定义一个与Python的range类似的函数makeRange。调用makeRange(n)返回一个生成器:

1
2
3
4
5
6
7
8
9
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()(就像您可以传递任何可迭代的值一样):

1
2
>>> list(makeRange(5))
[0, 1, 2, 3, 4]

比较示例与"只是返回一个列表"

上面的例子可以被认为仅仅是创建一个列表,你添加并返回:

1
2
3
4
5
6
7
8
9
10
11
12
# 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是列表理解的最后一部分,所有的生成器都是iterable的,所以它们通常是这样使用的:

1
2
3
#                   _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),或者在while循环中使用yield关键字实现。

请注意:生成器实际上可以用于更多的事情,例如实现协程、非确定性编程或其他优雅的事情。然而,我在这里提出的"懒惰列表"观点是您会发现的最常见的用法。

幕后

这就是"Python迭代协议"的工作原理。也就是说,当您执行list(makeRange(5))时发生了什么。这就是我前面描述的"懒惰的增量列表"。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> 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()函数(以及迭代协议的其他部分)来实现一些奇特的东西,这通常是以可读性为代价的,所以尽量避免这样做……

细节

正常情况下,大多数人不会在意下面的区别,可能想要停止阅读这里。

在python语言中,迭代器是任何"理解for循环概念"的对象,比如列表[1,2,3],迭代器是请求的for循环的特定实例,比如[1,2,3].__iter__()。生成器与任何迭代器完全相同,除了它的编写方式(使用函数语法)。

当您从列表中请求一个迭代器时,它将创建一个新的迭代器。然而,当您从迭代器请求一个迭代器时(您很少这样做),它只会给您一个自身的副本。

因此,在不太可能的情况下,您不能做这样的事情……

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

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


What does the yield keyword do in Python?

<

回答提纲/总结/ hh1 >调用带有yield的函数时,将返回生成器。生成器是迭代器,因为它们实现了迭代器协议,所以您可以对它们进行迭代。生成器还可以发送信息,使其在概念上成为协同程序。在python3中,您可以使用yield from在两个方向上从一个生成器委托给另一个生成器。(附录中给出了几个答案,包括最上面的一个,并讨论了在生成器中使用return。)

发电机:

yield只在函数定义中合法,在函数定义中包含yield使其返回生成器。

生成器的概念来自其他语言(参见脚注1),它们的实现各不相同。在Python的生成器中,代码的执行被冻结在生成点。当调用生成器(方法将在下面讨论)时,执行将恢复,然后在下一个yield中冻结。

yield了实现迭代器协议的简单方法,定义如下两种方法:__iter__next (python2)或__next__ (python3).这两种方法使对象成为一个迭代器,您可以使用Iterator抽象基对其进行类型检查类,该类来自collections模块。

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> 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.

生成器类型是迭代器的子类型:

1
2
3
>>> import collections, types
>>> issubclass(types.GeneratorType, collections.Iterator)
True

如果有必要,我们可以这样打字检查:

1
2
3
4
>>> isinstance(gen, types.GeneratorType)
True
>>> isinstance(gen, collections.Iterator)
True

Iterator的一个特性是,一旦耗尽,你就不能重用或重置它:

1
2
3
4
>>> list(gen)
['I am', 'a generator!']
>>> list(gen)
[]

如果你想再次使用它的功能,你必须再做一个(见脚注2):

1
2
>>> list(func())
['I am', 'a generator!']

可以通过编程方式生成数据,例如:

1
2
3
def func(an_iterable):
    for item in an_iterable:
        yield item

上面的简单生成器也等价于下面的——从Python 3.3开始(在Python 2中不可用),您可以使用yield from:

1
2
def func(an_iterable):
    yield from an_iterable

但是,yield from也允许授权给次级发电机,下一节将解释与子协同程序的合作委托。

协同程序:

yield形成一个表达式,该表达式允许将数据发送到生成器中(参见脚注3)

这里有一个例子,注意received变量,它将指向发送到生成器的数据:

1
2
3
4
5
6
7
8
9
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:

1
2
3
>>> first_year_interest = next(my_account)
>>> first_year_interest
50.0

现在我们可以把数据发送到生成器。(发送None与调用next相同。)

1
2
3
>>> next_year_interest = my_account.send(first_year_interest + 1000)
>>> next_year_interest
102.5

yield from合作委托子协同程序

现在,回想一下yield from在Python 3中是可用的。这允许我们委托协同程序到子协同程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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()

现在我们可以把功能委托给子生成器,它可以被使用由一个如上所述的发电机:

1
2
3
4
5
6
7
8
>>> 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

您可以在PEP 380中阅读更多关于yield from的精确语义的信息。

其他方法:关闭并抛出

close方法在函数处引发GeneratorExit执行被冻结。这也将被__del__ so you调用可以把任何清理代码,你处理GeneratorExit:

1
>>> my_account.close()

您还可以抛出一个可以在生成器中处理的异常或传送回用户:

1
2
3
4
5
6
7
8
9
10
>>> 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

结论

我认为我已经涵盖了以下问题的所有方面:

What does the yield keyword do in Python?

结果是yield做了很多。我确信我可以添加更多这方面的详细例子。如果你想要更多或有一些建设性的批评,请让我知道评论在下面。

附录:

评论顶部/接受的答案**它混淆了什么使一个迭代,仅仅使用一个列表作为例子。请参阅上面的参考文献,但总的来说:iterable有一个返回迭代器的__iter__方法。迭代器提供一个.next (python2)或.__next__ (python3)方法,该方法由for循环隐式地调用,直到它引发StopIteration,并且一旦它这样做了,它将继续这样做。然后,它使用生成器表达式来描述生成器是什么。因为生成器只是创建迭代器的一种方便的方法,所以它只会混淆问题,而且我们还没有讲到yield部分。在控制生成器耗尽时,他调用.next方法,而应该使用内置函数next。这将是一个合适的间接层,因为他的代码在python3中不能工作。Itertools吗?这与yield所做的完全无关。在Python 3中没有讨论yield提供的方法以及yield from中的新功能。最常见的答案是一个非常不完整的答案。对在生成器表达式或理解中建议yield的答案的评论。

语法目前允许列表理解中的任何表达式。

1
2
3
4
5
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

由于yield是一个表达式,一些人认为在理解或生成器表达式中使用它很有趣——尽管没有引用特别好的用例。

CPython核心开发人员正在讨论取消它的许可。以下是邮件列表中的相关邮件:

On 30 January 2017 at 19:05, Brett Cannon wrote:

On Sun, 29 Jan 2017 at 16:39 Craig Rodrigues wrote:

I'm OK with either approach. Leaving things the way they are in Python 3
is no good, IMHO.

我的意见是,这是一个SyntaxError,因为您没有得到您期望的结果语法。

我同意这是我们结束的一个明智的地方,就像任何代码一样依赖当前的行为实在是太聪明了易于维护。

为了达到这个目标,我们可能会想:

3.7中的SyntaxWarning或DeprecationWarning2.7.x中的Py3k警告SyntaxError在3.8

干杯,尼克。

——Nick Coghlan | ncoghlan在澳大利亚布里斯班| gmail.com上报道

此外,还有一个突出的问题(10544),它似乎指向了一个方向,即这从来都不是一个好主意(PyPy是用Python编写的Python实现,它已经发出了语法警告)。

底线是,除非CPython的开发人员告诉我们其他情况:不要将yield放在生成器表达式或理解中。

生成器中的return语句

在Python中2:

In a generator function, the return statement is not allowed to include an expression_list. In that context, a bare return indicates that the generator is done and will cause StopIteration to be raised.

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

在Python 3:

In a generator function, the return statement indicates that the generator is done and will cause StopIteration to be raised. The returned value (if any) is used as an argument to construct StopIteration and becomes the StopIteration.value attribute.

脚注

提案中引用了CLU、Sather和Icon三种语言将生成器的概念引入Python。总的思路是一个函数可以保持内部状态并产生中间状态数据点根据用户的需要而定。这在性能上肯定会更好到其他方法,包括Python线程,这在某些系统上甚至不可用。

例如,这意味着xrange对象(Python 3中的range)不是Iterator,即使它们是可迭代的,因为它们可以重用。和列表一样,它们的__iter__方法返回迭代器对象。

<子>yield最初是作为一个语句引入的,意思是它只能出现在代码块的行首。现在yield创建一个yield表达式。https://docs.python.org/2/reference/simple_stmts.html#grammar-token-yield_stmt此更改的目的是允许用户像发送数据一样将数据发送到生成器有人可能会收到。要发送数据,必须能够将其分配给某个东西,并且对于这种情况,声明是行不通的。


yield就像return一样——它返回您告诉它的任何内容(作为生成器)。不同之处在于,下一次调用生成器时,执行将从最后一次调用yield语句开始。与return不同的是,当产生一个yield时,堆栈帧不会被清除,但是控制会被转移回调用者,因此它的状态将在下一次调用函数时恢复。

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

list.extend调用迭代器,直到它耗尽为止。在您发布的代码示例中,只返回一个元组并将其添加到列表中会更清楚。


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

1
2
3
4
5
def fib():
    last, cur = 0, 1
    while True:
        yield cur
        last, cur = cur, last + cur

然后我可以在其他代码中这样使用它:

1
2
3
for f in fib():
    if some_condition: break
    coolfuncs(f);

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


对于那些喜欢最小工作示例的人,请考虑一下这个交互式Python会话:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
>>> 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;

而不是这样的:

1
2
3
4
5
6
def square_list(n):
    the_list = []                         # Replace
    for x in range(n):
        y = x * x
        the_list.append(y)                # these
    return the_list                       # lines

这样做:

1
2
3
4
def square_yield(n):
    for x in range(n):
        y = x * x
        yield y                           # with this one.

无论什么时候,当你发现自己从头开始构建一个列表时,yield每一块都要替换。

这是我第一次屈服地"啊哈"。

yield是一种甜蜜的说法

build a series of stuff

同样的行为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> for square in square_list(4):
...     print(square)
...
0
1
4
9
>>> for square in square_yield(4):
...     print(square)
...
0
1
4
9

不同的行为:

Yield是单遍:您只能迭代一次。当一个函数有一个收益率时,我们称它为生成器函数。迭代器就是它返回的东西。这些术语很能说明问题。我们失去了容器的便利性,但获得了按需要计算的级数的幂,而且任意长。

收益率是懒惰的,它推迟了计算。一个有收益率的函数在你调用它的时候根本不会执行。它返回一个迭代器对象,该对象会记住它在哪里停止。每次在迭代器上调用next()时(这发生在for循环中),执行距离下一个收益只有几英寸。return引发StopIteration并结束该系列(这是for循环的自然结束)。

收益率是多才多艺的。数据不必全部存储在一起,可以一次存储一个。它可以是无限的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> 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():

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

选择yield这个词很明智,因为这两个意思都适用:

yield — produce or provide (as in agriculture)

…提供本系列中的下一个数据。

yield — give way or relinquish (as in political power)

…在迭代器前进之前放弃CPU执行。


屈服给你一个发电机。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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循环中使用它,但是每个值只能访问一次。所有的值也不是同时存储在内存中;生成器对象"记住"上次调用它时它在循环中的位置——这样,如果您使用iterable(比方说)计数到500亿,您就不必一次计数到500亿并存储要计数的500亿个数。

再一次,这是一个很不自然的例子,如果你真的想计算到500亿,你可能会使用itertools。:)

这是生成器最简单的用例。正如您所说,它可以用来编写有效的排列,使用yield将内容向上推入调用堆栈,而不是使用某种堆栈变量。生成器还可以用于特殊的树遍历,以及其他所有方式。


它返回一个发电机。我对Python不是特别熟悉,但是如果您熟悉的话,我相信它和c#的迭代器块是一样的。

关键的思想是编译器/解释器/不管做什么,只要调用者关心,他们可以继续调用next(),它将继续返回值——就像生成器方法被暂停一样。现在很明显,你不能真正地"暂停"一个方法,所以编译器会为你建立一个状态机,让你记住你现在在哪里,以及局部变量是什么样子的等等。这比自己编写迭代器容易得多。


在描述如何使用生成器的众多答案中,有一种类型的答案我觉得还没有给出。下面是编程语言理论的答案:

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

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

延续,以这种更一般的形式,可以通过两种方式实现。在call/cc方法中,程序的堆栈按字面意思保存,然后当调用延续时,堆栈被恢复。

在延续传递风格(CPS)中,延续只是普通函数(仅在函数为第一类的语言中),程序员显式地管理这些函数并将其传递给子例程。在这种风格中,程序状态由闭包(以及恰好在其中编码的变量)表示,而不是驻留在堆栈上某个位置的变量。管理控制流的函数接受延续作为参数(在CPS的某些变体中,函数可能接受多个延续),并通过简单地调用它们并在调用后返回来操作控制流。延续传递风格的一个非常简单的例子如下:

1
2
3
4
5
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)

在这个(非常简单的)示例中,程序员将实际编写文件的操作保存为一个延续(这可能是一个非常复杂的操作,有许多细节需要编写),然后传递这个延续(i。e,作为一级闭包)到另一个操作符,该操作符执行更多的处理,然后在必要时调用它。(我在实际的GUI编程中经常使用这种设计模式,因为它可以节省代码行,更重要的是,可以在GUI事件触发后管理控制流。)

这篇文章的其余部分将把延续概念化为CPS,而不会失去概括性,因为它非常容易理解和阅读。

现在让我们讨论Python中的生成器。生成器是延续的特定子类型。而延续通常能够保存计算的状态(例如。,生成器只能通过迭代器保存迭代的状态。不过,对于生成器的某些用例,这个定义有点误导。例如:

1
2
3
def f():
  while True:
    yield 4

这显然是一个合理的迭代器,它的行为定义得很好——每次生成器对它进行迭代时,它都会返回4(并且永远如此)。但是,当想到迭代器(即迭代器)时,它可能不是脑海中出现的可迭代的原型类型。,for x in collection: do_something(x))。这个例子说明了生成器的强大功能:如果有任何东西是迭代器,那么生成器可以保存其迭代的状态。

重申:延续可以保存程序堆栈的状态,生成器可以保存迭代的状态。这意味着延续要比生成器强大得多,但生成器也要简单得多。它们对于语言设计人员更容易实现,对于程序员也更容易使用(如果您有一些时间,请尝试阅读和理解关于延续和调用/cc的这一页)。

但你可以很容易地实现(和概念化)生成器作为一个简单的,特定的情况下的延续传递风格:

无论何时调用yield,它都会告诉函数返回一个延续。当再次调用该函数时,它从停止的地方开始。生成器的next方法基本如下:

1
2
3
4
5
6
7
8
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关键字实际上是实际生成器函数的语法糖,基本上类似于:

1
2
3
4
5
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的迭代器协议的设计,该协议描述了如何按顺序请求值。

迭代器协议最著名的用户是Python中的for命令。所以,无论你什么时候做:

1
for item in sequence:

无论sequence是列表、字符串、字典还是上面描述的生成器对象;结果是一样的:您逐个从序列中读取项。

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

要获得更准确的信息,请阅读Python文档中的迭代器类型、yield语句和生成器。


虽然有很多答案显示了为什么要使用yield来创建生成器,但是yield有更多的用途。制作一个协程非常容易,它支持在两个代码块之间传递信息。我不会重复任何已经给出的关于使用yield创建生成器的好例子。

为了帮助理解yield在下面的代码中做了什么,您可以使用您的手指通过任何具有yield的代码来跟踪循环。每次您的手指碰到yield,您都必须等待输入nextsend。当调用next时,跟踪代码直到到达yield……然后计算yield右侧的代码并返回给调用者……然后等待。当再次调用next时,执行代码中的另一个循环。但是,您将注意到,在协程中,yield也可以与send一起使用,后者将从调用者向yield函数发送一个值。如果给定一个send,那么yield接收发送的值,并将其吐出到左边……然后跟踪代码,直到再次碰到yield为止(在末尾返回值,就像调用了next一样)。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>>> 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以来):

1
yield from <expr>

PEP 380—用于委托给子生成器的语法:

A syntax is proposed for a generator to delegate part of its operations to another generator. This allows a section of code containing 'yield' to be factored out and placed in another generator. Additionally, the subgenerator is allowed to return with a value, and the value is made available to the delegating generator.

The new syntax also opens up some opportunities for optimisation when one generator re-yields values produced by another.

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

1
2
3
async def new_coroutine(data):
   ...
   await blocking_action()

为了避免协程与常规生成器混淆(现在在两者中都使用yield)。


所有的答案都很好,但是对于新手来说有点难。

我想你已经学过return语句。

作为类比,returnyield是双胞胎。return表示"返回并停止",而"yield"表示"返回,但继续"

Try to get a num_list with return.

1
2
3
def num_list(n):
    for i in range(n):
        return i

运行该程序:

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

看,你只得到一个数字,而不是它们的列表。return从不让你快乐地获胜,只实现一次就退出。

There comes yield

return替换为yield:

1
2
3
4
5
6
7
8
9
10
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

One more step we can rewrite yield statement with return

1
2
3
4
5
6
7
8
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],但只能从'the object yield output'检索一次。因此,它有一个新名称generator对象,显示在Out[11]: 中。

最后,作为对grok的隐喻:

returnyield是双胞胎listgenerator是双胞胎


下面是一些Python示例,演示如何实际实现生成器,就好像Python没有为它们提供语法糖一样:

作为Python生成器:

1
2
3
4
5
6
7
8
9
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))

使用词法闭包而不是生成器

1
2
3
4
5
6
7
8
9
10
11
12
13
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)

使用对象闭包而不是生成器(因为closuresandobjectsareequal)

1
2
3
4
5
6
7
8
9
10
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's 'Python: Essential Reference'的第19页来快速描述生成器",但是其他很多人已经发布了很好的描述。

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

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


从编程的角度来看,迭代器被实现为thunks。

要将迭代器、生成器和线程池作为thunks(也称为匿名函数)来实现并发执行,可以使用发送到闭包对象的消息,该对象有一个dispatcher, dispatcher回答"messages"。

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

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

有很多方法可以实现这种计算。我使用了变异,但是不使用变异很容易做到,方法是返回当前值和下一个yield。

下面是一个使用R6RS结构的演示,但是其语义与Python完全相同。这是相同的计算模型,只需要修改语法就可以用Python重写它。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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
->

下面是一个简单的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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)

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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()时,它将函数的属性检索到堆栈并重新激活它。这个函数继续从它停止的地方计算,没有注意到它刚刚在冷库中度过了一个永恒的时间。

比较以下例子:

1
2
3
4
5
6
7
8
9
def normalFunction():
    return
    if False:
        pass

def yielderFunction():
    return
    if False:
        yield 12

当我们调用第二个函数时,它的行为与第一个函数非常不同。yield语句可能无法访问,但是如果它出现在任何地方,它将改变我们正在处理的内容的性质。

1
2
>>> yielderFunction()
<generator object yielderFunction at 0x07742D28>

调用yielderFunction()并不运行它的代码,而是用代码生成一个生成器。(也许用yielder前缀为可读性命名这样的东西是个好主意。)

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> 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函数如下:

1
2
3
def getNextLines():
   while con.isOpen():
       yield con.read()

你可以在你的代码中使用它,如下:

1
2
for line in getNextLines():
    doSomeThing(line)

执行控制转移收到

在执行yield时,执行控制将从getNextLines()转移到for循环。因此,每次调用getNextLines()时,执行都从上次暂停的地方开始。

因此,简而言之,函数具有以下代码

1
2
3
4
5
6
7
8
def simpleYield():
    yield"first time"
    yield"second time"
    yield"third time"
    yield"Now some useful value {}".format(12)

for i in simpleYield():
    print i

将打印

1
2
3
4
"first time"
"second time"
"third time"
"Now some useful value 12"

屈服是一个对象

函数中的return将返回一个值。

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

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

like barrier in the CUDA language, it will not transfer control until it gets
completed.

也就是说,它将从头运行函数中的代码,直到到达yield。然后,它将返回循环的第一个值。

然后,每个其他调用将再次运行您在函数中编写的循环,返回下一个值,直到没有任何值可以返回为止。


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

当在python函数中使用yield而不是return时,该函数就变成了称为generator function的特殊函数。该函数将返回一个generator类型的对象。yield关键字是一个标志,用来通知python编译器特别处理这样的函数。一旦返回某个值,普通函数将终止。但是在编译器的帮助下,生成器函数可以被认为是可恢复的。也就是说,执行上下文将被恢复,执行将从上次运行开始继续。直到显式调用return,否则将引发StopIteration异常(也是迭代器协议的一部分),或者到达函数的末尾。我找到了很多关于generator的参考文献,但是来自functional programming perspective的这个是最容易理解的。

(现在我想谈谈generator背后的理由,以及基于我自己理解的iterator。我希望这能帮助您掌握迭代器和生成器的基本动机。这种概念也出现在其他语言中,比如c#。)

据我所知,当我们想要处理一堆数据时,我们通常首先把数据存储在某个地方,然后一个一个地处理它。但这种天真的做法是有问题的。如果数据量很大,那么预先将它们作为一个整体存储将非常昂贵。因此,与其直接存储data本身,不如间接存储某种类型的metadata,即the logic how the data is computed

有两种方法可以包装这样的元数据。

在OO方法中,我们封装元数据as a class。这就是实现迭代器协议的所谓的iterator(即__next__()__iter__()方法)。这也是常见的迭代器设计模式。在函数方法中,我们封装元数据as a function。这是所谓的generator function。但是在底层,返回的generator object仍然是IS-A迭代器,因为它还实现了迭代器协议。

无论哪种方法,都会创建一个迭代器,即某个对象,它可以提供您想要的数据。OO方法可能有点复杂。无论如何,使用哪一个取决于您。


总之,yield语句将您的函数转换成一个工厂,该工厂生成一个特殊的对象,称为generator,它将包围原始函数的主体。迭代generator时,它将执行函数,直到到达下一个yield,然后暂停执行并计算传递给yield的值。它在每次迭代中重复这个过程,直到执行路径退出函数为止。例如,

1
2
3
4
5
6
7
def simple_generator():
    yield 'one'
    yield 'two'
    yield 'three'

for i in simple_generator():
    print i

简单地输出

1
2
3
one
two
three

能量来自于使用带有计算序列的循环的生成器,生成器每次执行停止循环以"生成"下一个计算结果,这样它就可以动态地计算一个列表,好处是为特别大型的计算节省了内存

假设你想创建一个你自己的range函数,生成一个可迭代的数字范围,你可以这样做,

1
2
3
4
5
6
7
def myRangeNaive(i):
    n = 0
    range = []
    while n < i:
        range.append(n)
        n = n + 1
    return range

像这样使用它;

1
2
for i in myRangeNaive(10):
    print i

但这是低效的,因为

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

幸运的是,圭多和他的团队足够慷慨,开发了发电机,所以我们可以这样做;

1
2
3
4
5
6
7
8
9
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",在下一个叫它将执行增量声明,跳回"同时",评估,和如果这是真的,它将停止和产量再次"n",将继续,直到条件返回false和发电机跳到函数的结束。


许多人使用return而不是yield,但在某些情况下,yield可以更高效、更容易使用。

下面是一个例子,yield绝对是最好的:

return (in function)

1
2
3
4
5
6
7
8
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

yield (in function)

1
2
3
4
5
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.

Calling functions

1
2
3
4
5
6
7
8
9
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使用了三行而不是五行,并且需要担心的变量少了一个。

This is the result from the code:

Output

可以看到,这两个函数做的是相同的事情。唯一的区别是return_dates()给出一个列表和yield_dates()给出一个生成器。

一个实际的例子可能是逐行读取文件,或者只是想生成一个生成器。


yield类似于函数的返回元素。不同之处在于,yield元素将函数转换为生成器。生成器的行为就像一个函数,直到"产生"某个东西。生成器将停止,直到下一次调用它,并从与开始时完全相同的位置继续。您可以通过调用list(generator())在其中获得所有"已生成"值的序列。


yield关键字只是收集返回的结果。把yield想象成return +=


这里有一个简单的基于yield的方法,来计算斐波那契数列,解释说:

1
2
3
4
5
def fib(limit=50):
    a, b = 0, 1
    for i in range(limit):
       yield b
       a, b = b, a+b

当您在REPL中输入这个并尝试调用它时,您将得到一个令人困惑的结果:

1
2
>>> fib()
<generator object fib at 0x7fa38394e3b8>

这是因为yield的出现向Python发出信号,表明您想要创建一个生成器,即一个按需生成值的对象。

那么,如何生成这些值呢?这可以通过使用内置函数next直接完成,也可以通过将其提供给使用值的构造来间接完成。

使用内置的next()函数,直接调用.next/__next__,强制生成器生成一个值:

1
2
3
4
5
6
7
8
9
10
11
>>> g = fib()
>>> next(g)
1
>>> next(g)
1
>>> next(g)
2
>>> next(g)
3
>>> next(g)
5

间接地,如果你提供fibfor循环,list初始化器,tuple初始化器,或者任何其他期望对象生成/产生值的东西,你将"消耗"生成器,直到它不能生成更多的值(并且返回):

1
2
3
4
5
results = []
for i in fib(30):       # consumes fib
    results.append(i)
# can also be accomplished with
results = list(fib(30)) # consumes fib

类似地,使用tuple初始化器:

1
2
>>> tuple(fib(5))       # consumes fib
(1, 1, 2, 3, 5)

生成器与函数的区别在于它是惰性的。它通过维护它的本地状态并允许您在需要时随时恢复来实现这一点。

当您第一次调用fib通过调用它:

1
f = fib()

Python编译函数,遇到yield关键字,然后返回生成器对象。看起来没什么用。

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

1
2
3
4
5
6
7
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:

1
>>> gen = yielder("Hello, yield!")

您现在有一个生成器对象,它正在等待一个命令来生成一个值。使用next,看看打印了什么:

1
2
3
4
>>> 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:

1
2
3
4
5
>>> 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

1
2
3
4
5
6
7
8
def f123():
    for _ in range(4):
        yield 1
        yield 2


for i in f123():
    print i

的输出是:

1
1 2 1 2 1 2 1 2

另一个TL,博士

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

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

您可以通过调用next将yield/generator看作是一种从外部手动运行控制流的方法(比如continue loop one step),无论流多么复杂。

注意:生成器不是一个正常的函数。它像本地变量(堆栈)一样记住以前的状态。详细说明请参阅其他答案或文章。生成器只能迭代一次。您可以不使用yield,但它不会很好,因此可以认为它是"非常好的"语言糖。


收益率与收益率相似。不同之处在于:

yield使函数可迭代(在下面的示例中primes(n = 1)函数变为可迭代的)。它本质上的意思是,下一次调用函数时,它将从它离开的地方继续(在yield expression行之后)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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)为真,它将返回素数。在下一个迭代中,它将从下一行继续

1
n += 1

这里所有的答案都很棒;但是其中只有一个(投票最多的一个)与代码的工作方式有关。另一些则与生成器有关,以及它们是如何工作的。

我不会重复生成器是什么,收益率是什么;我认为这些都被现有的答案所覆盖。然而,在花了几个小时试图理解与您的代码类似的代码之后,我将分解它的工作原理。

您的代码将遍历一个二叉树结构。以这棵树为例:

1
2
3
4
5
    5
   / \
  3   6
 / \   \
1   4   8

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

1
2
3
4
5
6
7
8
9
10
11
12
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__如下:

1
2
3
4
5
6
7
8
9
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将其翻译为

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

因为Node.__iter__函数是一个生成器,所以它内部的代码在每次迭代中执行。执行过程是这样的:

根元素是第一;检查它是否留下了childs和for迭代它们(让我们称它为it1,因为它是第一个迭代器对象)它有一个子元素,因此执行forfor child in self.leftself.left创建一个新的迭代器,它本身是一个节点对象(it2)与2相同的逻辑,并创建一个新的iterator (it3)现在我们到了树的左边。it3没有左子节点,所以它继续,而yield self.value在下一次调用next(it3)时,它将引发StopIteration,并且由于它没有正确的子元素而存在(它到达函数的末尾时不会产生任何结果)it1it2仍然活跃——它们没有耗尽,调用next(it2)将产生值,而不是提升StopIteration现在我们回到it2上下文,并调用next(it2),它在停止的地方继续:就在yield child语句之后。因为它已经没有剩余的孩子了,所以它继续繁殖,结果是self.val

这里的问题是,每次迭代都创建子迭代器来遍历树,并保存当前迭代器的状态。一旦到达终点,它将遍历堆栈,并以正确的顺序返回值(首先返回最小的yield值)。

您的代码示例用一种不同的技术做了类似的事情:它为每个子对象填充一个元素列表,然后在下一次迭代时弹出它并在当前对象上运行函数代码(因此是self)。

我希望这对这个传奇的话题有所贡献。我花了好几个小时来画这个过程来理解它。


简而言之,yield的用法类似于关键字return,只是它返回一个生成器。生成器对象只遍历一次。

收益有两个好处:

您不需要读取这些值两次;您可以获得许多子节点,而不需要将它们全部放入内存。


在Python中,generators(一种特殊类型的iterators)用于生成一系列值,而yield关键字与生成器函数的return关键字类似。

yield关键字做的另一件有趣的事情是保存生成器函数的state

因此,我们可以在每次generator生成时将number设置为不同的值。

这里有一个实例:

1
2
3
4
5
6
7
8
9
10
11
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))

产量

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> 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)
0
1
4

简而言之,您可以看到循环不会停止,甚至在发送对象或变量之后还会继续运行(不像return执行后循环会停止)。


yield是一种可以在python中使用的生成器类型。

这里有一个链接,看看产量的真正作用,也是在代。发电机,Yield关键字- Python中央(PC)

此外,yield的工作方式与return类似,但与return不同。如果你不太理解另一个,甚至还有一个链接可以解释yield更多。提高你的产量技能- jeffknupp


一个类比可以帮助我们理解这个概念:

想象一下,你创造了一台神奇的机器,它能够每天产生成千上万个灯泡。这台机器用一个独特的序列号在盒子里产生这些灯泡。你没有足够的空间同时储存所有这些灯泡。,由于存储空间的限制,您无法跟上机器的速度),因此您希望调整此机器以按需生成灯泡。

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

假设您有一个函数x,它为这些盒子生成惟一的序列号。显然,函数可以生成大量这样的条形码。一个更明智、更节省空间的选择是按需生成这些序列号。

机的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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()给定生成器来获取下一个序列号。

输出:

1
2
3
4
5
6
7
8
9
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类似于return值,但它对Generator有效。


在简单的yield中,返回生成器对象而不是值。

下面简单的例子将有所帮助!

1
2
3
4
5
6
7
8
def sim_generator():
    for i in range(3):
        yield(i)

obj = sim_generator()
next(obj) # another way is obj.__next__()
next(obj)
next(obj)

上面的代码返回0,1,2

甚至短

1
2
for val in sim_generator():
    print(val)

返回0,1,2

希望这有助于


yield收益率。就像有人让你做5杯蛋糕。如果你吃完了至少一个杯子蛋糕,你可以在做其他蛋糕的时候给他们吃。

1
2
3
4
5
6
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关键字出现在函数中时,它就变成了一个生成器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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'

他们吃光了所有的蛋糕,但又要了一块。

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

StopIteration:

他们被告知不要再问了。所以一旦你消耗了一个发电机,你就完成了。如果你想要更多的蛋糕,你需要再打一次电话。这就像下了另一个杯子蛋糕的订单。

1
2
3
4
5
6
7
8
In [13]: factory = make_cake(3)

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

您还可以使用for循环和上面的生成器。

再举一个例子:假设你想要一个随机密码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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,它可以生成无限个随机密码。所以我们也可以说,当我们不知道序列的长度时生成器是有用的,不像列表有有限数量的元素。


一个简单的生成器函数

1
2
3
4
5
6
7
8
9
10
11
12
13
def my_gen():
    n = 1
    print('This is printed first')
    # Generator function contains yield statements
    yield n

    n += 1
    print('This is printed second')
    yield n

    n += 1
    print('This is printed at last')
    yield n

yield语句暂停保存函数的所有状态,然后在以后的调用中继续。

https://www.programiz.com/python-programming/generator