有什么用 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 库。这是完整来源的链接: 模块空间.

有帮助吗?

解决方案

要了解什么 yield 是的,你必须明白什么 发电机 是。在了解生成器之前,您必须了解 可迭代对象.

可迭代对象

创建列表时,您可以一项一项地阅读其项目。一项一项地读取其项称为迭代:

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

mylist 是一个 可迭代的. 。当您使用列表推导式时,您会创建一个列表,从而创建一个可迭代对象:

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

您可以使用的一切“for... in...“ on 是一个可迭代对象; lists, strings, 、文件...

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

发电机

生成器是迭代器,是一种可迭代的 你只能迭代一次. 。生成器并不将所有值存储在内存中, 他们即时生成值:

>>> mygenerator = (x*x for x in range(3))
>>> for i in mygenerator:
...    print(i)
0
1
4

除了你用过之外,它是一样的 () 代替 []. 。但是你 不能 履行 for i in mygenerator 第二次,因为生成器只能使用一次:他们计算0,然后忘记它并计算1,最后计算4,一一计算。

屈服

yield 是一个关键字,用法如下 return, ,但该函数将返回一个生成器。

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

这是一个无用的示例,但是当您知道您的函数将返回一组只需读取一次的巨大值时,它会很方便。

掌握 yield, ,你必须明白 当您调用该函数时,您在函数体中编写的代码不会运行。 该函数仅返回生成器对象,这有点棘手:-)

然后,您的代码将从每次停止的地方继续 for 使用发电机。

现在是困难的部分:

第一次 for 调用从您的函数创建的生成器对象,它将从头开始运行您的函数中的代码,直到它命中 yield, ,然后它将返回循环的第一个值。然后,每次调用都会再次运行您在函数中编写的循环,并返回下一个值,直到没有值可返回为止。

一旦函数运行,生成器就被认为是空的,但没有命中 yield 不再了。这可能是因为循环已经结束,或者因为您不满足 "if/else" 不再了。


你的代码解释了

发电机:

# Here you create the method of the node object that will return the generator
def _get_child_candidates(self, distance, min_dist, max_dist):

    # Here is the code that will be called each time you use the generator object:

    # If there is still a child of the node object on its left
    # AND if distance is ok, return the next child
    if self._leftchild and distance - max_dist < self._median:
        yield self._leftchild

    # If there is still a child of the node object on its right
    # AND if distance is ok, return the next child
    if self._rightchild and distance + max_dist >= self._median:
        yield self._rightchild

    # If the function arrives here, the generator will be considered empty
    # there is no more than two values: the left and the right children

呼叫者:

# Create an empty list and a list with the current object reference
result, candidates = list(), [self]

# Loop on candidates (they contain only one element at the beginning)
while candidates:

    # Get the last candidate and remove it from the list
    node = candidates.pop()

    # Get the distance between obj and the candidate
    distance = node._get_dist(obj)

    # If distance is ok, then you can fill the result
    if distance <= max_dist and distance >= min_dist:
        result.extend(node._values)

    # Add the children of the candidate in the candidates list
    # so the loop will keep running until it will have looked
    # at all the children of the children of the children, etc. of the candidate
    candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))

return result

这段代码包含几个智能部分:

  • 循环在列表上迭代,但在迭代循环时列表会扩展:-)这是一种遍历所有这些嵌套数据的简洁方法,即使它有点危险,因为您可能会陷入无限循环。在这种情况下, candidates.extend(node._get_child_candidates(distance, min_dist, max_dist)) 耗尽生成器的所有值,但是 while 不断创建新的生成器对象,这些对象将产生与以前的值不同的值,因为它没有应用于同一节点。

  • extend() method 是一个列表对象方法,它需要一个可迭代对象并将其值添加到列表中。

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

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

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

  1. 您不需要两次读取这些值。
  2. 您可能有很多孩子,但您不希望将它们全部存储在内存中。

它之所以有效,是因为 Python 不关心方法的参数是否是列表。Python 需要迭代,因此它可以与字符串、列表、元组和生成器一起使用!这称为鸭子类型,也是 Python 如此酷的原因之一。但这是另一个故事,对于另一个问题......

您可以停在这里,或者阅读一点内容以了解生成器的高级用法:

控制发电机耗尽

>>> class Bank(): # Let's create a bank, building ATMs
...    crisis = False
...    def create_atm(self):
...        while not self.crisis:
...            yield "$100"
>>> hsbc = Bank() # When everything's ok the ATM gives you as much as you want
>>> corner_street_atm = hsbc.create_atm()
>>> print(corner_street_atm.next())
$100
>>> print(corner_street_atm.next())
$100
>>> print([corner_street_atm.next() for cash in range(5)])
['$100', '$100', '$100', '$100', '$100']
>>> hsbc.crisis = True # Crisis is coming, no more money!
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> wall_street_atm = hsbc.create_atm() # It's even true for new ATMs
>>> print(wall_street_atm.next())
<type 'exceptions.StopIteration'>
>>> hsbc.crisis = False # The trouble is, even post-crisis the ATM remains empty
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> brand_new_atm = hsbc.create_atm() # Build a new one to get back in business
>>> for cash in brand_new_atm:
...    print cash
$100
$100
$100
$100
$100
$100
$100
$100
$100
...

笔记: 对于 Python 3,请使用print(corner_street_atm.__next__()) 或者 print(next(corner_street_atm))

它对于各种事情都很有用,例如控制对资源的访问。

Itertools,你最好的朋友

itertools 模块包含操作可迭代对象的特殊函数。曾经想要复制一个生成器吗?连接两个发电机?用单行代码将嵌套列表中的值分组? Map / Zip 无需创建另一个列表?

然后就 import itertools.

一个例子?让我们看看四匹马比赛可能的到达顺序:

>>> horses = [1, 2, 3, 4]
>>> races = itertools.permutations(horses)
>>> print(races)
<itertools.permutations object at 0xb754f1dc>
>>> print(list(itertools.permutations(horses)))
[(1, 2, 3, 4),
 (1, 2, 4, 3),
 (1, 3, 2, 4),
 (1, 3, 4, 2),
 (1, 4, 2, 3),
 (1, 4, 3, 2),
 (2, 1, 3, 4),
 (2, 1, 4, 3),
 (2, 3, 1, 4),
 (2, 3, 4, 1),
 (2, 4, 1, 3),
 (2, 4, 3, 1),
 (3, 1, 2, 4),
 (3, 1, 4, 2),
 (3, 2, 1, 4),
 (3, 2, 4, 1),
 (3, 4, 1, 2),
 (3, 4, 2, 1),
 (4, 1, 2, 3),
 (4, 1, 3, 2),
 (4, 2, 1, 3),
 (4, 2, 3, 1),
 (4, 3, 1, 2),
 (4, 3, 2, 1)]

理解迭代的内部机制

迭代是一个暗示可迭代的过程(实现 __iter__() 方法)和迭代器(实现 __next__() 方法)。可迭代对象是可以从中获取迭代器的任何对象。迭代器是允许您迭代可迭代对象的对象。

这篇文章中有更多关于它的内容 如何 for 循环工作.

其他提示

理解的捷径 yield

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

  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. 获取一个迭代器 mylist:

    称呼 iter(mylist) -> 这会返回一个带有 a 的对象 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() 方法最终可能看起来非常复杂并且容易出现错误。在这里,生成器提供了一种干净且简单的解决方案。

这样想:

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

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

我应该注意,为了说明的目的, 过于简单化了。 :)

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

  1. 如果编译器在函数内检测到 yield 关键字 where ,则该函数不再通过 return 语句返回。 相反 立即会返回懒惰的“待处理列表”对象称为生成器
  2. 生成器是可迭代的。什么是 iterable ?它类似于 list set range 或dict-view,带有内置协议,用于访问a中的每个元素某个订单
  3. 简而言之:生成器是一个惰性的,递增挂起的列表 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是列表推导的最后一部分,并且所有生成器都是可迭代的,所以它们经常被这样使用:

    #                   _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))时会发生什么。这就是我之前描述的“懒惰,增量列表”。

    >>> 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中, iterable 是任何“理解for-loop”概念的对象。像列表 [1,2,3] iterator 是所请求的for循环的特定实例,如 [1,2,3]。 __iter __()生成器与任何迭代器完全相同,除了它的编写方式(使用函数语法)。

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

    因此,在不太可能的情况下,你没有做到

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

答案大纲/摘要

  • 一个函数 yield, ,当被调用时, 返回一个 发电机.
  • 生成器是迭代器,因为它们实现了 迭代器协议, ,这样你就可以迭代它们。
  • 发电机也可以 已发送信息, ,使其在概念上成为 协程.
  • 在Python 3中,你可以 代表 从一台发电机到另一台发电机双向 yield from.
  • (附录批评了几个答案,包括最重要的一个,并讨论了 return 在发电机中。)

发电机:

yield 仅在函数定义内部合法,并且 包括 yield 在函数定义中使其返回一个生成器。

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

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

与子协程合作委托 yield from

现在,回想一下 yield from 在 Python 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 fromPEP 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 做了很多。我相信我还能补充更多 这方面的例子很详尽。如果您想了解更多信息或有建设性的批评意见,请在评论中告诉我 下图


附录:

对最佳/已接受答案的批评**

  • 它对什么使一个 可迭代的, ,仅以列表为例。请参阅我上面的参考资料,但总结一下:一个可迭代对象有一个 __iter__ 方法返回一个 迭代器. 。一个 迭代器 提供了一个 .next (Python 2 或 .__next__ (Python 3) 方法,隐式调用 for 循环直到它升高 StopIteration, ,而且一旦发生,就会继续发生。
  • 然后它使用生成器表达式来描述生成器是什么。由于生成器只是一种创建 迭代器, ,这只会让事情变得混乱,而我们还没有达到目的 yield 部分。
  • 控制发电机耗尽 他称 .next 方法,当他应该使用内置函数时, next. 。这将是一个适当的间接层,因为他的代码不能在 Python 3 中运行。
  • 迭代工具?这与什么无关 yield 根本没有。
  • 没有讨论方法 yield 提供新功能 yield from 在Python 3中。 最佳/接受的答案是一个非常不完整的答案。

对答案建议的批评 yield 在生成器表达式或理解式中。

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

expr_stmt: testlist_star_expr (annassign | augassign (yield_expr|testlist) |
                     ('=' (yield_expr|testlist_star_expr))*)
...
yield_expr: 'yield' [yield_arg]
yield_arg: 'from' test | testlist

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

CPython 核心开发人员是 讨论废弃其津贴。这是邮件列表中的相关帖子:

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

2017 年 1 月 29 日星期日 16:39 克雷格·罗德里格斯 (Craig Rodrigues) 写道:

我对这两种方法都满意。保持 Python 3 中的原样 在我看来,这是不好的。

我认为是语法错误,因为你没有从 语法。

我同意这是我们的明智选择,因为任何代码 依赖于当前的行为实在是太聪明了,以至于 可维护。

为了实现这一目标,我们可能需要:

  • 3.7 中的语法警告或弃用警告
  • 2.7.x 中的 Py3k 警告
  • 3.8 中的语法错误

干杯,尼克。

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

此外,还有一个 未决问题 (10544) 这似乎指向这个方向 绝不 是个好主意(PyPy,一个用 Python 编写的 Python 实现,已经引发了语法警告。)

最重要的是,直到 CPython 的开发人员告诉我们不然: 不要放 yield 在生成器表达式或理解式中。

return 生成器中的语句

蟒蛇2:

在生成器函数中, return 语句不允许包含 expression_list. 。在这种背景下,一个裸露的 return 表示生成器已完成并将导致 StopIteration 待提高。

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

蟒蛇3:

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

脚注

  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 这一变化是 建议的 允许用户向生成器发送数据,就像 一个人可能会收到它。要发送数据,必须能够将其分配给某个对象,并且 为此,声明是行不通的。

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

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

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

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

def fib():
    last, cur = 0, 1
    while True: 
        yield cur
        last, cur = cur, last + cur

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

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

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

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

>>> def f():
...   yield 1
...   yield 2
...   yield 3
... 
>>> g = f()
>>> for i in g:
...   print i
... 
1
2
3
>>> for i in g:
...   print i
... 
>>> # Note that this time nothing was printed

<强> 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 代替每一个。

这是我的第一个“aha”收益的时刻。


yield 含糖的说法

  

构建一系列东西

同样的行为:

>>> for square in square_list(4):
...     print(square)
...
0
1
4
9
>>> for square in square_yield(4):
...     print(square)
...
0
1
4
9

不同的行为:

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

收益率懒惰,它会推迟计算。当你调用它时,一个带有的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))
...
0
1
4
9

如果你需要多次传递并且系列不是太长,只需在其上调用 list()

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

明确选择 yield 一词,因为这两个含义申请:

  

收益&#8212;生产或提供(如农业)

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

  

收益&#8212;放弃或放弃(如政治权力)

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

Yield为您提供发电机。

def get_odd_numbers(i):
    return range(1, i, 2)
def yield_odd_numbers(i):
    for x in range(1, i, 2):
       yield x
foo = get_odd_numbers(10)
bar = yield_odd_numbers(10)
foo
[1, 3, 5, 7, 9]
bar
<generator object yield_odd_numbers at 0x1029c6f50>
bar.next()
1
bar.next()
3
bar.next()
5

如您所见,在第一种情况下, foo 一次将整个列表保存在内存中。对于包含5个元素的列表来说,这不是什么大问题,但是如果你想要一个500万的列表呢?这不仅是一个巨大的内存消耗者,而且在调用函数时也需要花费大量时间来构建。

在第二种情况下, bar 只给你一个生成器。生成器是可迭代的 - 这意味着您可以在中使用循环等,但每个值只能访问一次。所有值也不会同时存储在内存中;生成器对象“记住”你最后一次调用它时在循环中的位置 - 这样,如果你使用一个可迭代(比如说)计数到500亿,你不必一次计算到500亿,并存储50数十亿个数字。

同样,这是一个非常人为的例子,如果你真的想要数到500亿,你可能会使用itertools。 :)

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

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

Python中的 yield 语句返回一个生成器。 Python中的生成器是一个返回 continuations 的函数(特别是一种协程,但是continuation代表了解更新的一般机制)。

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

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

本文的其余部分将不失一般性地将延续概念化为CPS,因为它更容易理解和阅读。

结果

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

def f():
  while True:
    yield 4

这显然是一个合理的迭代,其行为定义得很好 - 每次生成器迭代它时,它返回4(并且永远这样做)。但是,在考虑迭代器时,可能不会想到可迭代的典型类型(即,集合中的x的:do_something(x))。这个例子说明了生成器的强大功能:如果有什么是迭代器,生成器可以保存迭代的状态。

重申:Continuations可以保存程序堆栈的状态,生成器可以保存迭代状态。这意味着continuation比生成器更强大,但生成器也很多,更容易。它们对于语言设计者来说更容易实现,并且t

这是一个简单语言的例子。我将提供高级人类概念与低级 Python 概念之间的对应关系。

我想对一系列数字进行操作,但我不想因为创建该序列而打扰自己,我只想专注于我想做的操作。所以,我执行以下操作:

  • 我打电话给你,告诉你我想要一个以特定方式生成的数字序列,然后我让你知道算法是什么。
    这一步对应的是 def生成生成器函数,即函数包含一个 yield.
  • 稍后,我告诉你,“好吧,准备好告诉我数字的顺序”。
    此步骤对应于调用返回生成器对象的生成器函数。 请注意,您还没有告诉我任何数字;你只需拿起你的纸和铅笔。
  • 我问你“告诉我下一个数字”,你告诉我第一个数字;之后,你等我问你下一个号码。你的工作就是记住你在哪里,你已经说过什么数字,以及下一个数字是什么。我不关心细节。
    该步骤对应调用 .next() 在生成器对象上。
  • ...重复上一步,直到...
  • 最终,你可能会走到尽头。你不告诉我一个数字;你只需大喊:“放马!我受够了!没有更多的数字了!”
    此步骤对应于生成器对象结束其工作,并引发 StopIteration 例外 生成器函数不需要引发异常。当函数结束或发出一个错误时它会自动引发 return.

这就是生成器的作用(一个包含 yield);它开始执行,每当执行时就会暂停 yield, ,当被要求时 .next() 它的值从最后一个点继续。它在设计上与 Python 的迭代器协议完美契合,该协议描述了如何顺序请求值。

迭代器协议最著名的用户是 for Python 中的命令。因此,每当您执行以下操作时:

for item in sequence:

没关系,如果 sequence 是列表、字符串、字典或生成器 目的 如上所述;结果是一样的:您逐一读取序列中的项目。

注意 def正在编写一个函数,其中包含 yield 关键字并不是创建生成器的唯一方法;这只是创建一个最简单的方法。

欲了解更准确的信息,请阅读 迭代器类型, , 这 收益声明发电机 在 Python 文档中。

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

为了帮助理解以下代码中 yield 的作用,您可以用手指在任何具有 yield 的代码中跟踪循环。每当您的手指触及 yield 时,您必须等待输入 next send 。当调用 next 时,您将跟踪代码,直到您点击 yield &#8230; yield 右侧的代码被评估并返回给调用者&#8230;然后你等当再次调用 next 时,您将通过代码执行另一个循环。但是,您会注意到在协程中, yield 也可以与 send &#8230;一起使用。它将从调用者发送一个值到让步函数。如果给出 send ,则 yield 接收发送的值,并将其吐出左侧&#8230;然后通过代码的跟踪进行,直到再次点击 yield (最后返回值,就像调用 next 一样)。

例如:

>>> def coroutine():
...     i = -1
...     while True:
...         i += 1
...         val = (yield i)
...         print("Received %s" % val)
...
>>> sequence = coroutine()
>>> sequence.next()
0
>>> sequence.next()
Received None
1
>>> sequence.send('hello')
Received hello
2
>>> sequence.close()

还有另一个 yield 用法和意义(自Python 3.3起):

yield from <expr>

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

  

建议生成器将其部分操作委托给另一个生成器。这允许将包含'yield'的代码段分解出来并放在另一个生成器中。此外,允许子生成器返回一个值,该值可供委派生成器使用。

     

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

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

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

避免协程与常规生成器混淆(今天 yield 用于两者)。

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

我假设你已经学会了 return 陈述。

打个比方, returnyield 是双胞胎。 return 意思是“返回并停止”,而“yield”意思是“返回,但继续”

  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 运行您计划的时间。你可以解读 return 作为 return 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>.

总之,作为一个比喻来理解它:

  • returnyield 是双胞胎
  • listgenerator 是双胞胎

以下是一些如何实际实现生成器的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:Essential Reference'第19页,快速描述生成器”,但是很多其他人已经发布了很好的描述。

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

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

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

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

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

&QUOT; &QUOT;是发送到闭包的消息,由“ 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_code gi_frame 字段是存储冻结状态的位置。使用 dir(..)探索它们,我们可以确认我们上面的心理模型是可信的。

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

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

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

for line in getNextLines():
    doSomeThing(line)

执行控制转移问题

执行yield时,执行控件将从getNextLines()传递到 for 循环。因此,每次调用getNextLines()时,执行都会从上次暂停时开始执行。

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

def simpleYield():
    yield "first time"
    yield "second time"
    yield "third time"
    yield "Now some useful value {}".format(12)

for i in simpleYield():
    print i

将打印

"first time"
"second time"
"third time"
"Now some useful value 12"

收益是一个对象

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

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

更重要的是, yield 障碍

  像CUDA语言中的障碍一样,它不会转移控制直到它获得   完成。

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

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

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

当在python函数中使用 yield 而不是 return 时,该函数将变成一个特殊的名为 generator function 的东西。该函数将返回 generator 类型的对象。 yield 关键字是一个标志,用于通知python编译器专门处理此类函数。正常函数将在从其返回某个值后终止。但是在编译器的帮助下,生成器函数可以被认为是可恢复的。也就是说,将恢复执行上下文,并且执行将从上次运行继续。直到你显式调用return,这将引发 StopIteration 异常(它也是迭代器协议的一部分),或者到达函数的末尾。我找到了很多关于 generator 的参考资料,但是这个一个函数式编程角度来看是最易消化的。

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

据我所知,当我们想要处理大量数据时,我们通常先将数据存储在某处,然后逐个处理。但这种天真的方法存在问题。如果数据量很大,那么事先将它们作为一个整体存储起来是很昂贵的。 因此,不是直接存储 data 本身,为什么不间接存储某种元数据,即逻辑如何计算数据 即可。

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

  1. OO方法,我们将元数据包装为类。这是所谓的 iterator ,它实现了迭代器协议(即 __ next __() __ iter __()方法)。这也是常见的迭代器设计模式
  2. 功能方法,我们将元数据包装为函数。这是 所谓的生成器函数。但在引擎盖下,返回的生成器对象仍然是 IS-A 迭代器,因为它还实现了迭代器协议。
  3. 无论哪种方式,都会创建一个迭代器,即一些可以为您提供所需数据的对象。 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语句并yield'n',在下一次调用时它将执行increment语句,跳回'while',对其进行评估,如果为true,它将停止并再次yield'n',它将继续这种方式,直到 while 条件返回 false 并且生成器跳转到函数末尾。

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

以下是 yield 绝对最适合的示例:

  

返回(在功能中)

import random

def return_dates():
    dates = [] # With 'return' you need to create a list then return it
    for i in range(5):
        date = random.choice(["1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th", "10th"])
        dates.append(date)
    return dates
  

产量(在功能上)

def yield_dates():
    for i in range(5):
        date = random.choice(["1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th", "10th"])
        yield date # 'yield' makes a generator automatically which works
                   # in a similar way. This is much more efficient.
  

调用函数

dates_list = return_dates()
print(dates_list)
for i in dates_list:
    print(i)

dates_generator = yield_dates()
print(dates_generator)
for i in dates_generator:
    print(i)

两个函数都做同样的事情,但 yield 使用三行而不是五行,并且担心变量少一个。

  
    

这是代码的结果:

  

正如你所看到的,两个函数都做同样的事情。唯一的区别是 return_dates()给出一个列表, yield_dates()给出了一个生成器。

现实生活中的例子就像是逐行读取文件或者只是想制作一个生成器。

yield 就像一个函数的返回元素。不同之处在于, yield 元素将函数转换为生成器。在某些东西“屈服”之前,生成器的行为就像一个函数。发电机停止,直到下一次调用,并从它开始的完全相同的点继续。您可以通过调用 list(generator())来获取所有'yielding'值的序列。

yield 关键字只收集返回的结果。考虑 yield ,如 return + =

这是一个简单的 yield 方法,用于计算斐波那契数列,解释如下:

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

当你在REPL中输入它然后尝试调用它时,你会得到一个神秘的结果:

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

这是因为存在 yield ,表示要创建生成器的Python,即一个按需生成值的对象。

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

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

>>> g = fib()
>>> next(g)
1
>>> next(g)
1
>>> next(g)
2
>>> next(g)
3
>>> next(g)
5

间接地,如果你将 fib 提供给 for 循环, list 初始化器, tuple 初始化器,或者任何其他需要生成/生成值的对象的东西,你将“消费”生成器,直到它不再生成值(并返回):

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

类似地,使用元组初始值设定项:

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

生成器与函数的不同之处在于它是惰性的。它通过维护本地状态并允许您随时恢复来实现此目的。

当你第一次通过调用它来调用 fib 时:

f = fib()

Python编译函数,遇到 yield 关键字并简单地返回一个生成器对象。看起来不是很有帮助。

当你请求它直接或间接生成第一个值时,它会执行它找到的所有语句,直到遇到 yield ,然后它会返回你提供给的值。产量和暂停。有关更好地演示此示例的示例,让我们使用一些 print 调用(如果在Python 2上,则替换为 print&quot; text&quot; ):

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

一个简单的例子来理解它是什么: 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
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top