繁体   English   中英

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

What does the "yield" keyword do?

提示:本站收集StackOverFlow近2千万问答,支持中英文搜索,鼠标放在语句上弹窗显示对应的参考中文或英文, 本站还提供   中文繁体   英文版本   中英对照 版本,有任何建议请联系yoyou2525@163.com。

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方法时会发生什么? 是否返回列表? 单一元素? 又叫了吗? 后续调用何时停止?


1. 这段代码由 Jochen Schulz (jrschulz) 编写,他为度量空间制作了一个很棒的 Python 库。 这是完整源代码的链接: Module mspace
48 个回复

要了解yield的作用,您必须了解生成器是什么。 在你了解生成器之前,你必须了解iterables

可迭代对象

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

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

mylist是一个可迭代的 . 当您使用列表推导时,您会创建一个列表,因此是一个可迭代的:

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

您可以使用“ for... in... ” on 的所有内容都是可迭代的; listsstrings 、文件...

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

发电机

生成器是迭代器,一种只能迭代一次的可迭代对象。 生成器不会将所有值存储在内存中,它们会即时生成值

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

除了您使用()而不是[]之外,它是一样的。 但是,您不能for i in mygenerator第二次,因为生成器只能使用一次:它们计算 0,然后忘记它并计算 1,然后一个一个地结束计算 4。

屈服

yield是一个与return类似的关键字,除了该函数将返回一个生成器。

>>> def create_generator():
...    mylist = range(3)
...    for i in mylist:
...        yield i*i
...
>>> mygenerator = create_generator() # create a generator
>>> print(mygenerator) # mygenerator is an object!
<generator object create_generator 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 the distance is ok, return the next child
    if self._leftchild and distance - max_dist < self._median:
        yield self._leftchild

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

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

呼叫者:

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

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

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

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

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

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

return result

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

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

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

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

>>> 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 expr替换为result.append(expr)
  3. 在函数底部插入一行return result
  4. 是的 - 没有更多的yield声明! 阅读并找出代码。
  5. 将功能与原始定义进行比较。

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

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

一、迭代器协议——当你写

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

Python 执行以下两个步骤:

  1. 获取mylist的迭代器:

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

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

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

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

事实上,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

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

所以生成器对象有点像一个适配器——一方面它展示了迭代器协议,通过暴露__iter__()next()方法来保持for循环的运行。 然而,在另一端,它运行的函数刚好足以从中获取下一个值,并将其重新置于挂起模式。

为什么要使用生成器?

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

这样想:

对于具有next()方法的对象,迭代器只是一个听起来很花哨的术语。 所以一个 yielded 函数最终会是这样的:

原始版本:

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关键字,则该函数不再通过return语句返回。 相反,它立即返回一个称为生成器的惰性“待处理列表”对象
  2. 生成器是可迭代的。 什么是可迭代的? 它类似于listsetrange或字典视图,具有用于按特定顺序访问每个元素的内置协议

简而言之:生成器是一个惰性的、增量挂起的列表yield语句允许您使用函数表示法来编程生成器应该增量吐出的列表值

generator = myYieldingFunction(...)  # basically a list (but lazy)
x = list(generator)  # evaluate every element into a list

   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

基本上,每当遇到yield语句时,函数都会暂停并保存其状态,然后根据 python 迭代器协议发出“'list' 中的下一个返回值”(对于某些语法结构,例如重复调用next()的 for 循环next()并捕获StopIteration异常等)。 你可能遇到过带有生成器表达式的生成器; 生成器函数更强大,因为您可以将参数传回暂停的生成器函数,使用它们来实现协程。 稍后再谈。


基本示例(“列表”)

让我们定义一个函数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]

将示例与“仅返回列表”进行比较

上面的示例可以被认为只是创建一个您附加并返回的列表:

# return a list                  #  # return a generator
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
    return TO_RETURN             # 

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

但是,有一个主要区别; 见最后一节。


如何使用生成器

可迭代是列表推导的最后一部分,所有生成器都是可迭代的,因此它们经常这样使用:

#                  < ITERABLE >
>>> [x+10 for x in makeRange(5)]
[10, 11, 12, 13, 14]

为了更好地了解生成器,您可以使用itertools模块(确保在必要时使用chain.from_iterable而不是chain )。 例如,您甚至可以使用生成器来实现无限长的惰性列表,例如itertools.count() 您可以实现自己的def enumerate(iterable): zip(count(), iterable) ,或者在 while 循环中使用yield关键字来实现。

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


在幕后

这就是“Python 迭代协议”的工作原理。 也就是说,当您执行list(makeRange(5))时会发生什么。 这就是我之前所说的“惰性、增量列表”。

>>> x=iter(range(5))
>>> next(x)  # calls x.__next__(); x.next() is deprecated
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()函数(和迭代协议的其他部分)来实现花哨的东西,通常以牺牲可读性为代价,所以尽量避免这样做......


协程

协程示例:

def interactiveProcedure():
    userResponse = yield makeQuestionWebpage()
    print('user response:', userResponse)
    yield 'success'

coroutine = interactiveProcedure()
webFormData = next(coroutine)  # same as .send(None)
userResponse = serveWebForm(webFormData)

# ...at some point later on web form submit...

successStatus = coroutine.send(userResponse)

细节

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

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

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

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

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

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

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

答案大纲/摘要

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

发电机:

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

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

yield提供了一种实现迭代器协议的简单方法,由以下两种方法定义: __iter__next (Python 2) 或__next__ (Python 3)。 这两种方法都使对象成为迭代器,您可以使用collections模块中的Iterator抽象基类对它进行类型检查。

>>> 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):
    # must receive deposited value from .send():
    under_management = yield                   # yield None to start.
    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'''
            raise
        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'''
    # must queue up manager:
    next(manager)      # <- same as manager.send(None)
    # This is where we send the initial deposit to the manager:
    manager.send(deposited)
    try:
        yield from manager
    except GeneratorExit:
        return manager.close()  # delegate?

现在我们可以将功能委托给子生成器,它可以被生成器使用,就像上面一样:

my_manager = money_manager(.06)
my_account = investment_account(1000, my_manager)
first_year_return = next(my_account) # -> 60.0

现在模拟向帐户添加另外 1,000 加上帐户的回报 (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 6, in money_manager
  File "<stdin>", line 2, in <module>
ValueError

结论

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

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

事实证明, yield有很大作用。 我确信我可以为此添加更详尽的示例。 如果您想要更多或有一些建设性的批评,请通过下面的评论告诉我。


附录:

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

  • 仅以列表为例,对什么使iterable感到困惑。 请参阅上面的参考资料,但总而言之:可迭代对象有一个__iter__方法返回一个迭代器 迭代器提供了一个.next (Python 2 或.__next__ (Python 3) 方法,该方法被for循环隐式调用,直到它引发StopIteration ,一旦引发,它将继续这样做。
  • 然后它使用生成器表达式来描述生成器是什么。 由于生成器只是一种创建迭代器的便捷方式,它只会混淆问题,我们还没有进入yield部分。
  • 控制生成器耗尽中,他调用了.next方法,而他应该使用内置函数next 这将是一个适当的间接层,因为他的代码在 Python 3 中不起作用。
  • 迭代工具? 这与yield的作用完全无关。
  • 没有讨论yield yield from的方法以及 Python 3 中的新功能yield。top/accepted 答案是一个非常不完整的答案。

对建议在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 中保持原样是不好的。

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

我同意这对我们来说是一个明智的选择,因为任何依赖于当前行为的代码都非常聪明,无法维护。

就到达那里而言,我们可能希望:

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

干杯,尼克。

——尼克·科格兰 | gmail.com 上的 ncoghlan | 澳大利亚布里斯班

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

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

生成器中的return语句

Python 2中:

在生成器函数中, return语句不允许包含expression_list 在这种情况下,一个简单的return表示生成器已经完成,并将导致StopIteration被引发。

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

Python 3中:

在生成器函数中, return语句指示生成器已完成并将引发StopIteration 返回的值(如果有)用作构造StopIteration的参数并成为StopIteration.value属性。

脚注

  1. 提案中引用了 CLU、Sather 和 Icon 语言,以将生成器的概念引入 Python。 一般的想法是,一个函数可以维护内部状态并根据用户的需要产生中间数据点。 这承诺在性能上优于其他方法,包括 Python 线程,这在某些系统上甚至不可用。

  2. 这意味着,例如, range对象不是Iterator s,即使它们是可迭代的,因为它们可以被重用。 像列表一样,它们的__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调用迭代器,直到它用尽。 对于您发布的代码示例,只返回一个元组并将其附加到列表中会更清楚。

还有一件事要提一下:产生的函数实际上不必终止。 我写过这样的代码:

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;博士

而不是这个:

def square_list(n):
    the_list = []                         # Replace
    for x in range(n):
        y = x * x
        the_list.append(y)                # these
    return the_list                       # lines

做这个:

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

每当您发现自己从头开始构建列表时,请改为yield每个部分。

这是我的第一个“啊哈”时刻。


yield是一种含糖的说法

构建一系列东西

相同的行为:

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

不同的行为:

产量是单次通过:您只能迭代一次。 当一个函数中有一个 yield 时,我们称它为生成器函数 它返回的是一个迭代器 这些条款很有启发性。 我们失去了容器的便利性,但获得了根据需要计算且任意长度的序列的能力。

产量是懒惰的,它推迟了计算。 当你调用一个带有 yield 的函数时,它实际上根本不会执行。 它返回一个 迭代器对象,该对象记住它停止的位置。 每次在迭代器上调用next() (这发生在 for 循环中)时,执行都会向前移动到下一个 yield。 return引发 StopIteration 并结束系列(这是 for 循环的自然结束)。

产量是多才多艺的。 数据不必全部存储在一起,可以一次提供一个。 它可以是无限的。

>>> def squares_all_of_them():
...     x = 0
...     while True:
...         yield x * x
...         x += 1
...
>>> squares = squares_all_of_them()
>>> for _ in range(4):
...     print(next(squares))
...
0
1
4
9

如果您需要多次传递并且系列不是太长,只需调用list()就可以了:

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

出色地选择了yield这个词,因为这两种含义都适用:

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

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

yield - 让位或放弃(如在政治权力中)

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

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

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

它正在返回一个生成器。 我对 Python 不是特别熟悉,但如果你熟悉的话,我相信它与C# 的迭代器块是同一种东西。

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

在描述如何使用生成器的许多很好的答案中,有一种我觉得还没有给出的答案。 这是编程语言理论的答案:

Python 中的yield语句返回一个生成器。 (and specifically a type of coroutine, but continuations represent the more general mechanism to understand what is going on). Python 中的生成器是一个返回的函数(特别是一种协程,但延续代表了更通用的机制来理解正在发生的事情)。

编程语言理论中的延续是一种更为基础的计算,但它们并不经常使用,因为它们极难推理,也很难实现。 但是关于什么是延续的想法很简单:它是尚未完成的计算状态。 在这种状态下,变量的当前值、尚未执行的操作等都被保存下来。 然后在程序稍后的某个时间点,可以调用延续,以便程序的变量重置为该状态并执行保存的操作。

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

在延续传递风格 (CPS) 中,延续只是程序员显式管理并传递给子例程的普通函数(仅在函数是第一类的语言中)。 在这种风格中,程序状态由闭包(以及恰好编码在其中的变量)表示,而不是驻留在堆栈中某处的变量。 管理控制流的函数接受延续作为参数(在 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 和 call/cc 的页面)。

但是您可以轻松地将生成器实现(和概念化)作为延续传递样式的简单、特定情况:

每当调用yield时,它都会告诉函数返回一个延续。 当再次调用该函数时,它会从它停止的地方开始。 所以,在pseudo-pseudocode(即不是pseudocode,但不是code)中,生成器的next方法基本如下:

class Generator():
  def __init__(self,iterable,generatorfun):
    self.next_continuation = lambda:generatorfun(iterable)

  def next(self):
    value, next_continuation = self.next_continuation()
    self.next_continuation = next_continuation
    return value

其中yield关键字实际上是真正的生成器函数的语法糖,基本上是这样的:

def generatorfun(iterable):
  if len(iterable) == 0:
    raise StopIteration
  else:
    return (iterable[0], lambda:generatorfun(iterable[1:]))

请记住,这只是伪代码,Python 中生成器的实际实现更为复杂。 但是作为一个了解正在发生的事情的练习,请尝试使用延续传递样式来实现生成器对象,而不使用yield关键字。

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

我想对一个数字序列进行操作,但我不想为创建该序列而烦恼,我只想专注于我想做的操作。 因此,我执行以下操作:

  • 我打电话给你,告诉你我想要一个以特定方式计算的数字序列,我让你知道算法是什么。
    此步骤对应于定义yield def函数。
  • 过了一段时间,我告诉你,“好吧,准备告诉我数字的顺序”。
    此步骤对应于调用返回生成器对象的生成器函数。 请注意,您还没有告诉我任何数字; 你只要拿起你的纸和铅笔。
  • 我问你,“告诉我下一个号码”,你告诉我第一个号码; 之后,你等我问你下一个号码。 你的工作是记住你在哪里,你已经说过什么数字,下一个数字是什么。 我不在乎细节。
    此步骤对应于在生成器对象上调用next(generator)
    (在 Python 2 中, .next是生成器对象的一个​​方法;在 Python 3 中,它被命名为.__next__ ,但调用它的正确方法是使用内置的next()函数,就像len().__len__
  • … 重复上一步,直到…
  • 最终,你可能会走到尽头。 你不告诉我一个数字; 你只需大喊,“抓住你的马!我完了!没有更多的数字了!”
    此步骤对应于生成器对象结束其工作并引发StopIteration异常。
    生成器函数不需要引发异常。 当函数结束或发出return时,它会自动引发。

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

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

for item in sequence:

sequence是列表、字符串、字典还是生成器对象都没有关系,就像上面描述的那样; 结果是一样的:你从一个序列中一个一个地读取项目。

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

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

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

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

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. 尝试使用return获取 num_list 。
def num_list(n):
    for i in range(n):
        return i

运行:

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

看,你只得到一个数字而不是它们的列表。 return永远不会让你快乐地占上风,只执行一次就退出。

  1. yield

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. 再一步,我们可以用return重写yield语句
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是双胞胎

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

为了实现迭代器、生成器和用于并发执行的线程池等作为 thunk,使用发送到具有分派器的闭包对象的消息,并且分派器响应“消息”

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

有很多方法可以实现这种计算。 我使用了突变,但是可以通过返回当前值和下一个 yielder(使其引用透明)来进行这种没有突变的计算。 Racket 在某些中间语言中使用了初始程序的一系列转换,其中一种重写使得 yield 运算符可以用更简单的运算符转换为某种语言。

这是一个如何重写 yield 的演示,它使用 R6RS 的结构,但语义与 Python 的相同。 它是相同的计算模型,只需要更改语法即可使用 Python 的 yield 重写它。

 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) '(ab)) 'OK -> (stream 'yield) 'a -> (stream 'yield) 'b -> (stream 'yield) 'END -> (stream 'yield) 'END ->

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

作为 Python 生成器:

from itertools import islice

def fib_gen():
    a, b = 1, 1
    while True:
        yield a
        a, b = b, a + b

assert [1, 1, 2, 3, 5] == list(islice(fib_gen(), 5))

使用词法闭包代替生成器

def ftake(fnext, last):
    return [fnext() for _ in xrange(last)]

def fib_gen2():
    #funky scope due to python2.x workaround
    #for python 3.x use nonlocal
    def _():
        _.a, _.b = _.b, _.a + _.b
        return _.a
    _.a, _.b = 0, 1
    return _

assert [1,1,2,3,5] == ftake(fib_gen2(), 5)

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

class fib_gen3:
    def __init__(self):
        self.a, self.b = 1, 1

    def __call__(self):
        r = self.a
        self.a, self.b = self.b, self.a + self.b
        return r

assert [1,1,2,3,5] == ftake(fib_gen3(), 5)

我打算发布“阅读 Beazley 的“Python:基本参考”的第 19 页,以快速描述生成器”,但许多其他人已经发布了很好的描述。

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

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

这是一个简单的例子:

def isPrimeNumber(n):
    print "isPrimeNumber({}) call".format(n)
    if n==1:
        return False
    for x in range(2,n):
        if n % x == 0:
            return False
    return True

def primes (n=1):
    while(True):
        print "loop step ---------------- {}".format(n)
        if isPrimeNumber(n): yield n
        n += 1

for n in primes():
    if n> 10:break
    print "wiriting result {}".format(n)

输出:

loop step ---------------- 1
isPrimeNumber(1) call
loop step ---------------- 2
isPrimeNumber(2) call
loop step ---------------- 3
isPrimeNumber(3) call
wiriting result 3
loop step ---------------- 4
isPrimeNumber(4) call
loop step ---------------- 5
isPrimeNumber(5) call
wiriting result 5
loop step ---------------- 6
isPrimeNumber(6) call
loop step ---------------- 7
isPrimeNumber(7) call
wiriting result 7
loop step ---------------- 8
isPrimeNumber(8) call
loop step ---------------- 9
isPrimeNumber(9) call
loop step ---------------- 10
isPrimeNumber(10) call
loop step ---------------- 11
isPrimeNumber(11) call

我不是 Python 开发人员,但在我看来, yield占据了程序流的位置,下一个循环从“yield”位置开始。 似乎它正在那个位置等待,就在此之前,在外面返回一个值,下一次继续工作。

这似乎是一种有趣且不错的能力:D

下面是yield作用的一个心理形象。

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

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

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

所以这是生成器所依赖的一种冻结函数。

随后调用next()时,它会将函数的所有内容检索到堆栈中并重新对其进行动画处理。 该函数继续从它停止的地方开始计算,忘记了它刚刚在冷库中度过了永恒的事实。

比较以下示例:

def normalFunction():
    return
    if False:
        pass

def yielderFunction():
    return
    if False:
        yield 12

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

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

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

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

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

想象一下,你创造了一台非凡的机器,它每天能够产生成千上万个灯泡。 机器在具有唯一序列号的盒子中生成这些灯泡。 您没有足够的空间同时存储所有这些灯泡,因此您希望对其进行调整以按需生成灯泡。

Python 生成器与这个概念没有太大区别。 想象一下,您有一个名为barcode_generator的函数,它为盒子生成唯一的序列号。 显然,您可以通过该函数返回大量此类条形码,但受硬件 (RAM) 限制。 更明智且节省空间的选项是按需生成这些序列号。

机器代码:

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(barcode)位。

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

惰性迭代器

更准确地说,这个生成器是一个惰性迭代器 迭代器是帮助我们遍历对象序列的对象。 之所以称为惰性,是因为它在需要时才将序列中的所有项目加载到内存中。 上例中使用next是从迭代器中获取下一项的显式方式。 隐式方法是使用 for 循环:

for barcode in barcode_generator():
    print(barcode)

这将无限打印条形码,但您不会耗尽内存。

换句话说,生成器看起来像一个函数,但行为却像一个迭代器。

现实世界的应用?

最后,现实世界的应用? 当您处理大序列时,它们通常很有用。 想象一下从磁盘读取一个包含数十亿条记录的巨大文件。 在您可以使用其内容之前读取内存中的整个文件可能是不可行的(即,您将耗尽内存)。

一个简单的例子来理解它是什么: 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

就像每个答案所暗示的那样, 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"

(我下面的回答只是从使用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

有两种方法可以包装此类元数据。

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

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

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

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

for i in simple_generator():
    print i

简单地输出

one
two
three

力量来自使用带有计算序列的循环的生成器,生成器每次执行循环停止以“产生”计算的下一个结果,这样它就可以动态计算一个列表,好处是内存为特别大的计算而保存

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

def myRangeNaive(i):
    n = 0
    range = []
    while n < i:
        range.append(n)
        n = n + 1
    return range

并像这样使用它;

for i in myRangeNaive(10):
    print i

但这是低效的,因为

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

幸运的是 Guido 和他的团队足够慷慨地开发生成器,所以我们可以这样做;

def myRangeSmart(i):
    n = 0
    while n < i:
       yield n
       n = n + 1
    return

for i in myRangeSmart(10):
    print i

现在,在每次迭代时,生成器上的一个名为next()的函数都会执行该函数,直到它到达“yield”语句,在该语句中它停止并“生成”值或到达函数的末尾。 在这种情况下,在第一次调用时, next()执行到 yield 语句并产生 'n',在下一次调用时,它将执行增量语句,跳回 'while',评估它,如果为真,它将停止并再次产生'n',它将继续这种方式,直到while条件返回false并且生成器跳转到函数的末尾。

产量是一个对象

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

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

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

就像 CUDA 语言中的屏障一样,它在完成之前不会转移控制权。

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

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

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

这是一个yield绝对最适合的示例:

返回(在函数中)

import random

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

产量(在函数中)

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

调用函数

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

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

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

这是代码的结果:

输出

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

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

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

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

又一个 TL;DR

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

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

您可以通过调用next将 yield/generator 视为从外部手动运行控制流的一种方式(如继续循环一步),无论流程多么复杂。

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

这是一个简单的基于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循环)。

收益率类似于回报。 区别在于:

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

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

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

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

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

n += 1  

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

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

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

这是一个例子:

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

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

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

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

In [5]: factory = make_cake(5)

这里factory被称为发电机,它为你做蛋糕。 如果你调用make_function ,你会得到一个生成器而不是运行那个函数。 这是因为当函数中出现yield关键字时,它就变成了一个生成器。

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

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

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

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

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

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

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

StopIteration:

他们被告知不要再问了。 因此,一旦您使用了生成器,您就完成了它。 如果你想要更多蛋糕,你需要再次调用make_cake 这就像再次订购纸杯蛋糕一样。

In [13]: factory = make_cake(3)

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

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

再举一个例子:假设您在要求时想要一个随机密码。

In [22]: import random

In [23]: import string

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

In [25]: rpg = random_password_generator()

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

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

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

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

所以我不会重复生成器是什么或产量是什么。 我认为这些都被现有的很好的答案所涵盖。 但是,在花了几个小时试图理解与您的类似代码之后,我将分解它是如何工作的。

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

    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被执行。 self.left 中的for child in self.left self.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

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

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

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

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

实际上,正如这里的许多答案所解释的那样,使用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)

Python 中的yield关键字用于在不干扰局部变量状态的情况下退出代码,并且当再次调用该函数时,执行从我们离开代码的最后一点开始。

下面的例子演示了yield的工作:

def counter():
    x=2
    while x < 5:
        yield x
        x += 1
        
print("Initial value of x: ", counter()) 

for y in counter():
    print(y)

上面的代码生成下面的输出:

Initial value of x:  <generator object counter at 0x7f0263020ac0>
2
3
4

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

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 之后的语句开始。 在上面的示例中,打印了“First value printing”并调用了该函数。 1 被退回并打印。 然后打印“第二值打印”并再次调用 fun()。 它没有打印 1(第一条语句),而是返回 2,即紧接在 yield 1 之后的语句。同样的过程被进一步重复。

简单的答案

当函数包含至少一个yield语句时,该函数自动成为生成器函数。 当您调用生成器函数时,python 会执行生成器函数中的代码,直到出现yield语句。 yield语句冻结函数及其所有内部状态。 当您再次调用生成器函数时,python 会从冻结位置继续执行生成器函数中的代码,直到一次又一次地出现yield语句。 生成器函数执行代码,直到生成器函数在没有yield语句的情况下运行。

基准

创建一个列表并返回它:

def my_range(n):
    my_list = []
    i = 0
    while i < n:
        my_list.append(i)
        i += 1
    return my_list

@profile
def function():
    my_sum = 0
    my_values = my_range(1000000)
    for my_value in my_values:
        my_sum += my_value

function()

结果:

Total time: 1.07901 s
Timer unit: 1e-06 s

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
     9                                           @profile
    10                                           def function():
    11         1          1.1      1.1      0.0      my_sum = 0
    12         1     494875.0 494875.0     45.9      my_values = my_range(1000000)
    13   1000001     262842.1      0.3     24.4      for my_value in my_values:
    14   1000000     321289.8      0.3     29.8          my_sum += my_value



Line #    Mem usage    Increment  Occurences   Line Contents
============================================================
     9   40.168 MiB   40.168 MiB           1   @profile
    10                                         def function():
    11   40.168 MiB    0.000 MiB           1       my_sum = 0
    12   78.914 MiB   38.746 MiB           1       my_values = my_range(1000000)
    13   78.941 MiB    0.012 MiB     1000001       for my_value in my_values:
    14   78.941 MiB    0.016 MiB     1000000           my_sum += my_value

动态生成值:

def my_range(n):
    i = 0
    while i < n:
        yield i
        i += 1

@profile
def function():
    my_sum = 0
    
    for my_value in my_range(1000000):
        my_sum += my_value

function()

结果:

Total time: 1.24841 s
Timer unit: 1e-06 s

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
     7                                           @profile
     8                                           def function():
     9         1          1.1      1.1      0.0      my_sum = 0
    10
    11   1000001     895617.3      0.9     71.7      for my_value in my_range(1000000):
    12   1000000     352793.7      0.4     28.3          my_sum += my_value



Line #    Mem usage    Increment  Occurences   Line Contents
============================================================
     7   40.168 MiB   40.168 MiB           1   @profile
     8                                         def function():
     9   40.168 MiB    0.000 MiB           1       my_sum = 0
    10
    11   40.203 MiB    0.016 MiB     1000001       for my_value in my_range(1000000):
    12   40.203 MiB    0.020 MiB     1000000           my_sum += my_value

概括

生成器函数需要比返回列表的函数多一点的时间来执行,但它使用的内存要少得多。

一个简单的用例:

>>> def foo():
    yield 100
    yield 20
    yield 3

    
>>> for i in foo(): print(i)

100
20
3
>>> 

它是如何工作的:当被调用时,该函数立即返回一个对象。 该对象可以传递给 next() 函数。 每当调用 next() 函数时,您的函数都会运行到下一个 yield 并为 next() 函数提供返回值。

在底层,for 循环识别出该对象是一个生成器对象并使用 next() 来获取下一个值。

在一些像 ES6 和更高版本的语言中,它的实现略有不同,因此 next 是生成器对象的成员函数,您可以在每次获取下一个值时从调用者传递值。 因此,如果 result 是生成器,那么您可以执行类似 y = result.next(555) 的操作,并且产生值的程序可以说类似 z = yield 999。y 的值将是下一个从 yield 获得的 999,并且z 的值将是 555,yield 从下一个获得。 Python 的 get 和 send 方法具有类似的效果。

简单来说

yield 语句暂停函数的执行并将值发送回调用者,但保留足够的状态以使函数能够从中断的地方恢复。 恢复后,函数会在最后一次 yield 运行后立即继续执行。 这允许它的代码随着时间的推移产生一系列值,而不是一次计算它们并将它们像列表一样发送回来。

让我们看一个例子:

# A Simple Python program to demonstrate working
# of yield
  
# A generator function that yields 1 for the first time,
# 2 second time and 3 third time
def simpleGeneratorFun():
    yield 1
    yield 2
    yield 3
  

用于检查上述生成器功能的驱动程序代码

for value in simpleGeneratorFun(): 
    print(value)

Output:

1
2
3

Return 将指定的值发送回其调用者,而 Yield 可以生成一系列值。 当我们想要迭代一个序列但又不想将整个序列存储在内存中时,我们应该使用 yield。

产量用于 Python 生成器。 生成器函数的定义与普通函数一样,但每当需要生成值时,它都会使用 yield 关键字而不是 return。 如果 def 的主体包含 yield,则该函数自动成为生成器函数。

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

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

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

两者的输出将相同。

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

生成器允许立即获取单个已处理的项目(无需等待整个集合被处理)。 这在下面的示例中进行了说明。

import time

def get_gen():
    for i in range(10):
        yield i
        time.sleep(1)

def get_list():
    ret = []
    for i in range(10):
        ret.append(i)
        time.sleep(1)
    return ret


start_time = time.time()
print('get_gen iteration (individual results come immediately)')
for i in get_gen():
    print(f'result arrived after: {time.time() - start_time:.0f} seconds')
print()

start_time = time.time()
print('get_list iteration (results come all at once)') 
for i in get_list():
    print(f'result arrived after: {time.time() - start_time:.0f} seconds')

get_gen iteration (individual results come immediately)
result arrived after: 0 seconds
result arrived after: 1 seconds
result arrived after: 2 seconds
result arrived after: 3 seconds
result arrived after: 4 seconds
result arrived after: 5 seconds
result arrived after: 6 seconds
result arrived after: 7 seconds
result arrived after: 8 seconds
result arrived after: 9 seconds

get_list iteration (results come all at once)
result arrived after: 10 seconds
result arrived after: 10 seconds
result arrived after: 10 seconds
result arrived after: 10 seconds
result arrived after: 10 seconds
result arrived after: 10 seconds
result arrived after: 10 seconds
result arrived after: 10 seconds
result arrived after: 10 seconds
result arrived after: 10 seconds

yield关键字用于枚举/迭代,其中函数预计返回一个以上的输出。 我想引用这个非常简单的例子 A

# example A
def getNumber():
    for r in range(1,10):
        return r

上面的函数即使被多次调用也只会返回1 现在,如果我们将return替换为yield ,如示例 B

# example B
def getNumber():
    for r in range(1,10):
        yield r

它会在第一次调用时返回1 ,当再次调用时返回2 ,然后是34并且它会递增到 10。

尽管示例 B在概念上是正确的,但要在python 3中调用它,我们必须执行以下操作:


g = getNumber() #instance
print(next(g)) #will print 1
print(next(g)) #will print 2
print(next(g)) #will print 3

# so to assign it to a variables
v = getNumber()
v1 = next(v) #v1 will have 1
v2 = next(v) #v2 will have 2
v3 = next(v) #v3 will have 3

功能 - 返回。

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

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

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

要了解它的产量函数,必须了解什么是生成器。 此外,在了解生成器之前,您必须了解iterables Iterable: iterable 要创建一个列表,自然需要能够一个一个地读取每个元素。 逐一读取其项的过程称为迭代:

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

所有可用于... in... 的数据结构都是可迭代的; 列表、字符串、文件...

这些可迭代的方法很方便,因为您可以随意读取它们,但是您将所有值都存储在内存中,当您有很多值时,这并不总是可取的。 生成器:生成器 生成器也是迭代器的一种,一种特殊的迭代,只能迭代一次。 生成器不会将所有值存储在内存中,而是动态生成值:

发电机:发电机,发电机,发电机发电但不储存能量;)

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

只要使用 () 代替 [],列表推导就成为生成器推导。 但是,由于生成器只能使用一次,所以不能第二次执行 mygenerator 中的 for i:生成器计算 0,然后丢弃,然后计算 1,最后一次计算 4。典型的黑瞎子掰玉米.

yield 关键字的使用方式与 return 相同,只是该函数将返回生成器。

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

这个例子本身没什么用,但是当你需要一个函数返回大量的值并且只需要读取一次时,使用yield就变得方便了。

要掌握yield,需要明确一点,当一个函数被调用时,写在函数体中的代码是不会运行的。 该函数仅返回生成器对象。 初学者可能会对此感到困惑。

其次,了解代码每次使用生成器时都会从中断处继续。

现在最困难的部分是:

第一次 for 调用从你的函数创建的生成器对象,它会从头开始运行函数中的代码,直到它达到 yield,然后它会返回循环的第一个值。 然后,每个后续调用将运行您在函数中编写的循环的下一次迭代并返回下一个值。 这将一直持续到生成器被认为是空的,当函数运行时没有命中时产生。 那可能是因为循环已经结束,或者因为您不再对“if/else”感到满意。

个人理解希望对你有所帮助!

关键点

  • Python 的语法使用yield关键字的存在来创建一个返回生成器的函数。

  • 生成器是一种迭代器,这是 Python 中循环发生的主要方式。

  • 生成器本质上是一个可恢复的函数。 return返回一个值并结束一个函数不同, yield关键字返回一个值并暂停一个函数。

  • 当在生成器上调用next(g)时,该函数会从中断处恢复执行。

  • 只有当函数遇到显式或隐式return ,它才会真正结束。

编写和理解生成器的技术

理解和思考生成器的一种简单方法是使用print()而不是yield编写一个常规函数:

def f(n):
    for x in range(n):
        print(x)
        print(x * 10)

观察它的输出:

>>> f(3)
0
0
1
10
2
2

理解该函数后,将yield替换为print以获得生成相同值的生成器:

def f(n):
    for x in range(n):
        yield x
        yield x * 10

这使:

>>> list(f(3))
[0, 0, 1, 10, 2, 20]

迭代器协议

“yield 做什么”的答案可能很简短,但它是一个更大的世界的一部分,即所谓的“迭代器协议”。

在迭代器协议的发送方,有两种相关的对象。 可迭代对象是可以循环的东西。 迭代器是跟踪循环状态的对象。

在迭代器协议的消费者端,我们在可迭代对象上调用iter()来获取迭代器。 然后我们在迭代器上调用next()以从迭代器中检索值。 当没有更多数据时,会引发StopIteration异常:

>>> s = [10, 20, 30]    # The list is the "iterable"
>>> it = iter(s)        # This is the "iterator"
>>> next(it)            # Gets values out of an iterator
10
>>> next(it)
20
>>> next(it)
30
>>> next(it)
Traceback (most recent call last):
 ...
StopIteration

为了让这一切对我们更容易,for 循环代表我们调用 iter 和 next:

>>> for x in s:
...     print(x)
...   
10
20
30

一个人可以写一本关于这一切的书,但这些是重点。 当我教授 Python 课程时,我发现这是一个足以建立理解并立即开始使用它的最小解释。 特别是,使用print编写函数,对其进行测试,然后转换为yield的技巧似乎适用于所有级别的 Python 程序员。

yield用于创建generator 将生成器视为迭代器,它在每次迭代中为您提供价值。 当您在循环中使用 yield 时,您会得到一个生成器 object ,您可以使用它以迭代方式从循环中获取项目

1 “yield”关键字在 Julia 中有什么作用?

Julia 中yield关键字的用途是什么,它有什么作用? 我已经看到它在 Python 中使用过几次,然后在 Julia 中使用过,但它似乎在 Julia 中的使用环境非常不同。 ...

2020-03-18 16:07:50 1 234   julia
2 yield 关键字的奇妙之处

好的,当我在构建自定义枚举器时,我注意到了这种与产量有关的行为 假设你有这样的事情: 在技​​术上从函数返回之后,是什么让编译器能够在 while 循环中执行索引 ++ 和其余代码? ...

4 Python 中的普通 yield 关键字有什么作用?

根据Python 文档,yield 关键字可以采用“ expression_list ”,但它是可选的: 无论是在 Python 文档中, 还是在 Python 中的 yield 关键字做什么的任何答案中,或者从网上的一般阅读中,我都找不到此类用法的示例。 如果在没有expression_li ...

6 Python的“ yield”关键字来计算

酒吧店有十天的促销活动。 在此期间,啤酒的价格每天下降10%。 例如,第一天花费10美元的啤酒,第二天花费9美元,第三天花费8.1美元。 我想编写一个使用yield关键字每天计算啤酒价格的python函数。 例如,如果我们输入10,则我的预期输出是: ..等等 ...

7 C#:如何翻译Yield关键字

没有yield关键字,MSDN示例会是什么样子? 如果您愿意,可以使用任何示例。 我只想了解幕后发生的事情。 屈服运营商是否热切或懒惰地评估? 样品: MSDN - 产量关键字 如果热切评估yield运算符,我的猜测是: 我不知道如果屈服运算 ...

2011-01-13 16:59:29 4 876   c#/ yield
8 理解yield关键字和LINQ

我试图更好地理解yield关键字,我认为我对它有足够的理解,所以我进行了一些测试,但是我对结果感到惊讶。 如果我运行下面的代码,我会得到以下输出,表明它在整个范围内循环,而不仅仅是4号。 输出: 如果我运行下面的代码,它表明它只到达4然后停止。 输出: 我想 ...

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2022 STACKOOM.COM