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

时间:2008-10-23 22:21:12

标签: python iterator generator yield coroutine

Python中yield关键字的用途是什么?它做了什么?

例如,我正在尝试理解此代码 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时会发生什么? 列表是否返回?单个元素?它又被召唤了吗?后续呼叫何时停止?


<子> 这段代码是由Jochen Schulz(jrschulz)编写的,他为度量空间创建了一个很棒的Python库。这是指向完整来源的链接:Module mspace

44 个答案:

答案 0 :(得分:13408)

要了解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...”开启的所有内容都是可迭代的; 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。 / p>

产量

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

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

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

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

  1. 您无需两次读取值。
  2. 您可能有很多孩子,并且您不希望它们都存储在内存中。
  3. 它的工作原理是因为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是您可以从中获取迭代器的任何对象。迭代器是让你迭代迭代的对象。

    本文中有关于how for loops work的更多信息。

答案 1 :(得分:1793)

理解yield

的捷径

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

  1. 在功能开头插入一行result = []
  2. 将每个yield expr替换为result.append(expr)
  3. 在功能底部插入一行return result
  4. 耶 - 没有更多yield陈述!阅读并找出代码。
  5. 将功能与原始定义进行比较。
  6. 这个技巧可以让你了解函数背后的逻辑,但yield实际发生的情况与基于列表的方法中发生的情况明显不同。在许多情况下,yield方法将更高效,更快。在其他情况下,即使原始函数工作得很好,这个技巧也会让你陷入无限循环。继续阅读以了解更多信息......

    不要混淆您的Iterables,Iterators和Generators

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

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

    Python执行以下两个步骤:

    1. 获取mylist的迭代器:

      致电iter(mylist) - &gt;这将返回一个带有next()方法的对象(或Python 3中的__next__())。

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

    2. 使用迭代器循环遍历项目:

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

    3. 事实上,只要Python想要循环对象的内容,它就会执行上述两个步骤 - 因此它可能是for循环,但也可能是{{1}之类的代码}(其中otherlist.extend(mylist)是一个Python列表)。

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

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

      1. 内置列表,词典,元组,集合,文件。
      2. 实现self
      3. 的用户定义类
      4. 发电机。
      5. 请注意,__iter__()循环不知道它正在处理什么类型的对象 - 它只是遵循迭代器协议,并且很高兴在调用for时获得项目。内置列表逐个返回项目,字典逐个返回,文件逐个返回等。然后生成器返回...那就是next()的来源:

        yield

        而不是def f123(): yield 1 yield 2 yield 3 for item in f123(): print item 语句,如果yield中有三个return语句,则只会执行第一个语句,并且函数将退出。但f123()不是普通的功能。调用f123()时,不会返回yield语句中的任何值!它返回一个生成器对象。此外,该功能并没有真正退出 - 它进入暂停状态。当f123()循环试图遍历生成器对象时,该函数在其先前返回的for之后的最后一行从其挂起状态恢复,执行下一行代码,在这种情况下一个yield语句,并将其作为下一项返回。这一过程发生,直到函数退出,此时生成器引发yield,循环退出。

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

        为什么使用发电机?

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

答案 2 :(得分:469)

这样想:

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

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

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

答案 3 :(得分:397)

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

  1. 如果编译器在函数内部检测到yield关键字 ,则该函数不再通过return语句返回。 相反 立即会返回一个懒惰的“待处理列表”对象,称为生成器
  2. 生成器是可迭代的。什么是 iterable ?它类似于listsetrange或dict-view,带有内置协议,用于按特定顺序访问每个元素
  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关键字实现。

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


    幕后花絮

    这就是“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]迭代器[1,2,3].__iter__()所请求的for循环的特定实例。 生成器与任何迭代器完全相同,除了它的编写方式(使用函数语法)。

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

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

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

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

答案 4 :(得分:315)

  

yield关键字在Python中的作用是什么?

答案大纲/摘要

  • yield的功能,在调用时,会返回Generator
  • 生成器是迭代器,因为它们实现了iterator protocol,因此您可以迭代它们。
  • 生成器也可以发送信息,从概念上讲它是 coroutine
  • 在Python 3中,您可以 yield from 在两个方向上委托从一个生成器转移到另一个生成器。
  • (附录批评了几个答案,包括最重要的答案,并讨论了return在生成器中的使用。)

发生器:

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

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

yield提供了一个 implementing the iterator protocol的简单方法,由以下两种方法定义: __iter__next(Python 2)或__next__(Python 3)。这两种方法 使对象成为一个迭代器,您可以使用Iterator Abstract Base进行类型检查 来自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 is that once exhausted的一项功能,您无法重复使用或重置它:

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

现在我们可以将数据发送到生成器。 (Sending None is the same as calling next。):

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

使用yield from

与子协同程序进行合作授权

现在,回想一下,Python 3中提供了yield from。这允许我们委派 corcorines到subcorcorineine:

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

您可以在PEP 380.

中详细了解yield from的精确语义

其他方法:关闭并抛出

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提供的方法以及Python 3中的新功能yield from最高/接受的答案是一个非常不完整的答案。

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

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

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

由于屈服是一种表达,因此在一些人的吹捧中,在理解或生成器表达中使用它是有趣的 - 尽管没有引用特别好的用例。

CPython核心开发人员是discussing deprecating its allowance。 这是来自邮件列表的相关帖子:

  

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

     
    

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

         
      

我对任何一种方法都没关系。把事情留在Python 3中       不好,恕我直言。

    
         

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

  
     

我同意这对我们来说是一个明智的地方,就像任何代码一样   依靠当前的行为真的太聪明了   可维护性。

     

在达到目标方面,我们可能想要:

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

干杯,尼克。

     

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

此外,有一个outstanding issue (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介绍生成器的概念。一般的想法是 一个函数可以维持内部状态并产生中间函数 数据点由用户按需提供。这承诺是superior in performance to other approaches, including Python threading,在某些系统上甚至无法使用。

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

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

答案 5 :(得分:271)

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

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

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

答案 6 :(得分:207)

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

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

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

答案 7 :(得分:178)

对于那些喜欢最小工作示例的人,请冥想这个交互式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

答案 8 :(得分:172)

<强> 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是一种sugary方式

  

构建一系列东西

同样的行为:

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

不同的行为:

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

收益率懒惰,它会推迟计算。当你调用它时,一个带有的yield的函数实际上根本不会执行。它会返回一个iterator object来记住它停止的地方。每次在迭代器上调用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这个词,因为both meanings适用:

  

产量 - 生产或提供(如农业)

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

  

收益 - 放弃或放弃(如政治权力)

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

答案 9 :(得分:158)

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只给你一个发电机。生成器是可迭代的 - 这意味着您可以在for循环等中使用它,但每个值只能被访问一次。所有值也不会同时存储在内存中;生成器对象“记住”上次调用它时循环的位置 - 这样,如果你使用一个可迭代(比方说)计数到500亿,你就不必数到500亿全部立即存储500亿个数字。

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

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

答案 10 :(得分:153)

它正在返回一台发电机。我对Python并不是特别熟悉,但我相信如果你熟悉它,它与C#'s iterator blocks是一样的。

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

答案 11 :(得分:138)

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

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(并且永远这样做)。但是在考虑迭代器(即for x in collection: do_something(x))时,它可能不是想到的典型迭代类型。这个例子说明了生成器的强大功能:如果有什么是迭代器,生成器可以保存迭代的状态。

重申:Continuations可以保存程序堆栈的状态,生成器可以保存迭代状态。这意味着continuation比生成器更强大,但生成器也很多,更容易。语言设计人员更容易实现它们,程序员更容易使用它们(如果你有时间刻录,请尝试阅读和理解this page about continuations and 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关键字。

答案 12 :(得分:125)

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

我想对一系列数字进行操作,但我不想在创建该序列时打扰我自己,我只想专注于我想要做的操作。所以,我做了以下几点:

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

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

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

for item in sequence:

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

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

要获得更准确的信息,请阅读Python文档中的iterator typesyield statementgenerators

答案 13 :(得分:110)

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

为了帮助理解yield在以下代码中的作用,您可以用手指在任何具有yield的代码中跟踪循环。每当您的手指触及yield时,您都必须等待输入nextsend。当调用next时,您将跟踪代码,直到您点击yield ... yield右侧的代码被评估并返回给调用者...然后您等待。再次调用next时,您将通过代码执行另一个循环。但是,您会注意到,在协程中,yield也可以与send ...一起使用,它会将来自调用者的值发送到让步函数中。如果给出了send,那么yield会收到发送的值,并将其从左侧吐出...然后通过代码的跟踪进度直到您再次点击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()

答案 14 :(得分:102)

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

yield from <expr>

来自 PEP 380 -- Syntax for Delegating to a Subgenerator

  

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

     

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

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

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

避免协程与常规生成器混淆(今天yield都使用了)。

答案 15 :(得分:88)

所有伟大的答案,但对于新手来说有点困难。

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

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

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

运行它:

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

请注意,您只获得一个数字而不是它们的列表。 return永远不会让你高兴,只执行一次并退出。

  
      
  1. yield
  2.   

return替换为yield

In [10]: def num_list(n):
    ...:     for i in range(n):
    ...:         yield i
    ...:

In [11]: num_list(3)
Out[11]: <generator object num_list at 0x10327c990>

In [12]: list(num_list(3))
Out[12]: [0, 1, 2]

现在,你赢了所有数字。

与运行一次且停止的return相比,yield运行您计划的次数。 您可以将return解释为return one of them,将yield解释为return all of them。这称为iterable

  
      
  1. 我们可以使用yield
  2. 重写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是双胞胎

答案 16 :(得分:86)

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

答案 17 :(得分:82)

我打算发布“阅读Beazley的'Python:Essential Reference'第19页,以快速描述发生器”,但是很多其他人已经发布了很好的描述。

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

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

答案 18 :(得分:79)

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

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

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

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

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

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

Welcome to Racket v6.5.0.3.

-> (define gen
     (lambda (l)
       (define yield
         (lambda ()
           (if (null? l)
               'END
               (let ((v (car l)))
                 (set! l (cdr l))
                 v))))
       (lambda(m)
         (case m
           ('yield (yield))
           ('init  (lambda (data)
                     (set! l data)
                     'OK))))))
-> (define stream (gen '(1 2 3)))
-> (stream 'yield)
1
-> (stream 'yield)
2
-> (stream 'yield)
3
-> (stream 'yield)
'END
-> ((stream 'init) '(a b))
'OK
-> (stream 'yield)
'a
-> (stream 'yield)
'b
-> (stream 'yield)
'END
-> (stream 'yield)
'END
->

答案 19 :(得分:72)

这是一个简单的例子:

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

答案 20 :(得分:59)

这是yield所做的心理形象。

我喜欢将一个线程视为具有堆栈(即使它没有以这种方式实现)。

当调用普通函数时,它将其局部变量放在堆栈上,进行一些计算,然后清除堆栈并返回。其局部变量的值再也看不到了。

使用yield函数,当其代码开始运行时(即在调用函数之后,返回一个生成器对象,然后调用其next()方法),它同样将其局部变量放到堆栈和计算一段时间。但是,当它到达yield语句时,在清除其部分堆栈并返回之前,它会获取其局部变量的快照并将它们存储在生成器对象中。它还会在其代码中写下它当前所处的位置(即特定的yield语句)。

所以它是发电机悬挂的一种冻结功能。

随后调用next()时,它会将函数的所有物检索到堆栈中并重新设置动画。该功能继续从它停止的地方进行计算,不知道它刚刚在冷库中度过了永恒的事实。

比较以下示例:

def normalFunction():
    return
    if False:
        pass

def yielderFunction():
    return
    if False:
        yield 12

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

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

调用yielderFunction()不会运行其代码,但会从代码中生成一个生成器。 (为了便于阅读,最好使用yielder前缀命名这些内容。)

>>> gen = yielderFunction()
>>> dir(gen)
['__class__',
 ...
 '__iter__',    #Returns gen itself, to make it work uniformly with containers
 ...            #when given to a for loop. (Containers return an iterator instead.)
 'close',
 'gi_code',
 'gi_frame',
 'gi_running',
 'next',        #The method that runs the function's body.
 'send',
 'throw']

gi_codegi_frame字段是存储冻结状态的位置。通过dir(..)探索它们,我们可以确认我们上面的心理模型是可信的。

答案 21 :(得分:50)

如同每个答案所示,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"

答案 22 :(得分:44)

收益是一个对象

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

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

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

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

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

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

答案 23 :(得分:44)

(我的下面的回答只是从使用Python生成器的角度讲,而不是underlying implementation of generator mechanism,这涉及到堆栈和堆操作的一些技巧。)

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

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

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

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

  1. OO方法,我们包装元数据as a class。这是实现迭代器协议的所谓iterator(即__next__()__iter__()方法)。这也是常见的iterator design pattern
  2. 功能方法,我们包装元数据as a function。这是 所谓的generator function。但在引擎盖下,返回的generator object仍为IS-A迭代器,因为它还实现了迭代器协议。
  3. 无论哪种方式,都会创建一个迭代器,即一些可以为您提供所需数据的对象。 OO方法可能有点复杂。无论如何,使用哪一个取决于你。

答案 24 :(得分:43)

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

def simple_generator():
    yield 'one'
    yield 'two'
    yield 'three'

for i in simple_generator():
    print i

简单输出

one
two
three

电源来自使用带有计算顺序的循环的发电机,发电机每次都执行循环停止以产生&#39;计算的下一个结果,通过这种方式,它可以动态计算列表,其好处是为特别大的计算节省了内存

假设你想创建一个自己的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()的生成器上的函数执行该函数,直到它达到“收益率”为止。声明停止并且产生&#39;值或到达函数的结尾。在这种情况下,在第一次调用时,next()执行yield语句并产生&#39; n&#39;,在下次调用时它将执行增量语句,跳回到&#39;而& #39;,评估它,如果是真的,它将停止并产生&#39; n&#39;再次,它将继续这种方式,直到while条件返回false并且生成器跳转到函数的末尾。

答案 25 :(得分:41)

许多人使用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使用三行而不是五行,并且担心变量少一个。

  
    

这是代码的结果:

  

Output

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

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

答案 26 :(得分:36)

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

答案 27 :(得分:35)

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

答案 28 :(得分:32)

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

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

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

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

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

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

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

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

间接地,如果您向fib循环,for初始值设定项,list初始值设定项或其他需要生成/生成值的对象的其他内容提供tuple ,你将“消耗”生成器,直到它不再生成值(并返回):

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

同样,使用tuple初始化程序:

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

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

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

f = fib()

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

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

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

答案 29 :(得分:31)

一个简单的例子,它可以很容易地解释: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

答案 30 :(得分:30)

又一个TL; DR

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

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

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

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

答案 31 :(得分:24)

产量与回报类似。区别在于:

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

def isprime(n):
    if n == 1:
        return False
    for x in range(2, n):
        if n % x == 0:
            return False
    else:
        return True

def primes(n = 1):
   while(True):
       if isprime(n): yield n
       n += 1 

for n in primes():
    if n > 100: break
    print(n)

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

n += 1  

答案 32 :(得分:11)

这里的所有答案都很棒;但只有其中一个(投票最多的一个)与代码如何工作有关。其他一般与 generator 有关,以及它们是如何工作的。

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

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

    5
   / \
  3   6
 / \   \
1   4   8

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

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

    yield self.val

    if self.has_right_child():
        for child in self.right:
            yield child

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

def __iter__(self):

    class EmptyIter():
        def next(self):
            raise StopIteration

    if self.root:
        return self.root.__iter__()
    return EmptyIter()

while candidates语句可以替换为for element in tree; Python将此转换为

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

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

  1. 根元素是第一个;检查它是否已离开子节点并for迭代它们(让它称之为it1,因为它是第一个迭代器对象)
  2. 它有一个孩子,所以for被执行。 for child in self.leftself.left创建新迭代器,这是一个Node对象本身(it2)
  3. 与2相同的逻辑,并创建一个新的iterator(it3)
  4. 现在我们到达了树的左端。 it3没有留下孩子,所以继续yield self.value
  5. 在下次调用next(it3)时,它会引发StopIteration并存在,因为它没有正确的子项(它到达函数的末尾而没有产生任何结果)
  6. it1it2仍然有效 - 他们没有用尽,调用next(it2)会产生价值,而不会提高StopIteration
  7. 现在我们回到it2上下文,并在next(it2)语句之后调用继续停止的yield child。由于它没有剩下的孩子,它会继续并产生它self.val
  8. 这里的问题是每次迭代创建子迭代器来遍历树,并保持当前迭代器的状态。一旦到达末尾,它就会遍历堆栈,并以正确的顺序返回值(最小的收益率值)。

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

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

答案 33 :(得分:8)

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

另一个引人入胜的关键字yield是保存生成器函数的state

因此,每次number产生收益时,我们都可以将generator设置为不同的值。

这是一个实例:

def getPrimes(number):
    while True:
        if isPrime(number):
            number = yield number     # a miracle occurs here
        number += 1

def printSuccessivePrimes(iterations, base=10):
primeGenerator = getPrimes(base)
primeGenerator.send(None)
for power in range(iterations):
    print(primeGenerator.send(base ** power))

答案 34 :(得分:8)

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

实际上,如此处许多答案所述,使用yield会创建generator

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

示例:

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

def translator():
    # load all the words in English language and the translation to 'other lang'
    my_words_dict = {'hello': 'hello in other language', 'dog': 'dog in other language'}

    while True:
        word = (yield)
        yield my_words_dict.get(word, 'Unknown word...')

运行:

my_words_translator = translator()

next(my_words_translator)
print(my_words_translator.send('dog'))

next(my_words_translator)
print(my_words_translator.send('cat'))

将打印:

dog in other language
Unknown word...

总结一下:

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

答案 35 :(得分:7)

第一个收益计划

def countdown_gen(x):
      count = x
      while count > 0:
           yield count
           count -= 1

g = countdown_gen(5)

for item in g:
     print(item)

输出

5
4
3
2
1

了解流量

enter image description here

注意

  1. 返回生成器obj挂起函数执行并保存其执行
  2. 状态,可以再次执行功能。

答案 36 :(得分:6)

产量

>>> 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不同)。 / p>

答案 37 :(得分:6)

打个比方可以帮助理解这个想法:

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

Python生成器与这个概念没什么不同。

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

机器代码:

def barcode_generator():
    serial_number = 10000  # Initial barcode
    while True:
        yield serial_number
        serial_number += 1


barcode = barcode_generator()
while True:
    number_of_lightbulbs_to_generate = int(input("How many lightbulbs to generate? "))
    barcodes = [next(barcode) for _ in range(number_of_lightbulbs_to_generate)]
    print(barcodes)

    # function_to_create_the_next_batch_of_lightbulbs(barcodes)

    produce_more = input("Produce more? [Y/n]: ")
    if produce_more == "n":
        break

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

输出:

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

答案 38 :(得分:6)

yield关键字将替换函数定义中的return以创建生成器。

def create_generator():
   for i in range(100):
   yield i
myGenerator = create_generator()
print(myGenerator)
# <generator object create_generator at 0x102dd2480>
for i in myGenerator:
   print(i) # prints 0-99

当第一次使用返回的生成器时(不在赋值中,而是在for循环中),函数定义将一直执行到到达yield语句为止。在那里,它将暂停(请参阅为什么称为yield),直到再次使用为止。然后,它将在停下来的地方取纸。在生成器最后一次迭代后,yield命令之后的所有代码都将执行。

def create_generator():
   print("Beginning of generator")
   for i in range(3):
      yield i
   print("After yield")
print("Before assignment")

myGenerator = create_generator()

print("After assignment")
for i in myGenerator :
   print(i) # prints 0-99
"""
Before assignment
After assignment
Beginning of generator
0
1
2
After yield

yield 关键字可修改函数的行为,以生成在迭代过程中在每个yield命令上暂停的生成器。该功能只有在迭代后才能执行,这样可以改善资源管理,从而提高整体性能。使用生成器(和产生的函数)创建用于一次性迭代的大型数据集。

答案 39 :(得分:5)

python中的

yield在某种程度上类似于return语句,除了一些区别。如果必须从一个函数返回多个值,则return语句将所有值作为列表返回,并且必须将其存储在调用程序块的内存中。但是,如果我们不想使用额外的内存怎么办?相反,我们希望在需要时从函数中获取值。这就是产生收益的地方。请考虑以下函数:-

def fun():
   yield 1
   yield 2
   yield 3

呼叫者是:-

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

上面的代码段(调用函数)在被调用时输出:-

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

从上面可以看到,yield返回一个值给它的调用者,但是当再次调用该函数时,它不是从第一个语句开始,而是从yield之后的语句开始。在上面的示例中,打印了“第一价值打印”并调用了该函数。 1已退回并打印。然后打印“第二值打印”,并再次调用fun()。它不打印1(第一个语句),而是返回2,即在yield 1之后的语句。再次重复相同的过程。

答案 40 :(得分:2)

yield产生了一些东西。就像有人要您制作5个杯子蛋糕。如果您已完成至少一个杯子蛋糕的制作,则可以在制作其他蛋糕的同时让它们吃。

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

In [5]: factory = make_cake(5)

这里factory被称为生成器,它使您结块。如果调用make_function,则会得到一个生成器,而不是运行该函数。这是因为当函数中存在yield关键字时,它会成为生成器。

In [7]: next(factory)
Out[7]: 'Cake 0'

In [8]: next(factory)
Out[8]: 'Cake 1'

In [9]: next(factory)
Out[9]: 'Cake 2'

In [10]: next(factory)
Out[10]: 'Cake 3'

In [11]: next(factory)
Out[11]: 'Cake 4'

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

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

StopIteration:

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

In [13]: factory = make_cake(3)

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

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

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

In [22]: import random

In [23]: import string

In [24]: def random_password_generator():
    ...:     while True:
    ...:         yield ''.join([random.choice(string.ascii_letters) for _ in range(8)])
    ...:

In [25]: rpg = random_password_generator()

In [26]: for i in range(3):
    ...:     print(next(rpg))
    ...:
FXpUBhhH
DdUDHoHn
dvtebEqG

In [27]: next(rpg)
Out[27]: 'mJbYRMNo'

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

答案 41 :(得分:1)

简单的生成器功能

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

答案 42 :(得分:1)

通常,它用于创建函数外的迭代器。 将 'yield' 视为函数的 append() 并将函数视为数组。 如果满足某些条件,您可以在函数中添加该值以使其成为迭代器。

arr=[]
if 2>0:
   arr.append(2)

def func():
   if 2>0:
      yield 2

两者的输出相同。

使用 yield 的主要优点是创建迭代器。 迭代器在实例化时不计算每个项目的值。他们只在你要求的时候计算它。这称为惰性求值。

答案 43 :(得分:0)

功能 - 返回。

生成器 - 收益(包含一个或多个收益和零个或多个回报)。

names = ['Sam', 'Sarah', 'Thomas', 'James']


# Using function
def greet(name) :
    return f'Hi, my name is {name}.'
    
for each_name in names:
    print(greet(each_name))

# Output:   
>>>Hi, my name is Sam.
>>>Hi, my name is Sarah.
>>>Hi, my name is Thomas.
>>>Hi, my name is James.


# using generator
def greetings(names) :
    for each_name in names:
        yield f'Hi, my name is {each_name}.'
 
for greet_name in greetings(names):
    print (greet_name)

# Output:    
>>>Hi, my name is Sam.
>>>Hi, my name is Sarah.
>>>Hi, my name is Thomas.
>>>Hi, my name is James.

生成器看起来像一个函数,但行为像一个迭代器。

生成器从它离开(或产生)的地方继续执行。恢复后,该函数会在最后一次 yield 运行后立即继续执行。这允许它的代码随着时间的推移产生一系列值,而不是一次计算它们并将它们像列表一样发送回来。

def function():
    yield 1 # return this first
    yield 2 # start continue from here (yield don't execute above code once executed)
    yield 3 # give this at last (yield don't execute above code once executed)

for processed_data in function(): 
    print(processed_data)
    
#Output:

>>>1
>>>2
>>>3

注意: 产量不应该在 try ... finally 构造中。