python - yield递归 - yield用法




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

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

回答大纲/摘要

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

发电机:

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

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

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

>>> def func():
...     yield 'I am'
...     yield 'a generator!'
... 
>>> type(func)                 # A function with yield is still a function
<type 'function'>
>>> gen = func()
>>> type(gen)                  # but it returns a generator
<type 'generator'>
>>> hasattr(gen, '__iter__')   # that's an iterable
True
>>> hasattr(gen, 'next')       # and with .next (.__next__ in Python 3)
True                           # implements the iterator protocol.

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

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

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

>>> isinstance(gen, types.GeneratorType)
True
>>> isinstance(gen, collections.Iterator)
True

Iterator一个特性是,一旦耗尽 ,您就无法重用或重置它:

>>> list(gen)
['I am', 'a generator!']
>>> list(gen)
[]

如果您想再次使用其功能,则必须制作另一个(参见脚注2):

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

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

def func(an_iterable):
    for item in an_iterable:
        yield item

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

def func(an_iterable):
    yield from an_iterable

但是, yield from也允许委托给子发电机,这将在下面关于与子程序的协同授权的部分中解释。

协同程序:

yield表示允许将数据发送到生成器的表达式(参见脚注3)

下面是一个示例,请注意received变量,该变量将指向发送到生成器的数据:

def bank_account(deposited, interest_rate):
    while True:
        calculated_interest = interest_rate * deposited 
        received = yield calculated_interest
        if received:
            deposited += received


>>> my_account = bank_account(1000, .05)

首先,我们必须使用内置函数排队生成器, next 。 它将调用适当的next__next__方法,具体取决于您使用的Python版本:

>>> first_year_interest = next(my_account)
>>> first_year_interest
50.0

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

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

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

现在,回想一下,Python 3中提供了yield from 。这允许我们将协同程序委托给子协会:

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完全没有关系。
  • 在Python 3中没有讨论yield提供的方法以及新功能。 最高/接受的答案是一个非常不完整的答案。

对答案的批判表明在生成器表达或理解中的yield

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

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

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

CPython核心开发人员正在讨论弃用其配额 。 这是邮件列表中的相关帖子:

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

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

无论采用哪种方法,我都行。 恕我直言,将它们放在Python 3中的方式并不好。

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

我同意这对我们来说是一个明智的地方,因为任何依赖当前行为的代码实在太聪明而无法维护。

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

  • 3.7中的SyntaxWarning或DeprecationWarning
  • 2.7.x中的Py3k警告
  • 3.8中的SyntaxError

干杯,尼克。

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

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

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

生成器中的return语句

Python 2中

在生成器函数中,不允许return语句包含expression_list 。 在该上下文中,裸return表示生成器已完成并将导致StopIteration被引发。

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

Python 3中

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

脚注

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

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

  3. yield最初是作为语句引入的,这意味着它只能出现在代码块中一行的开头。现在yield创建一个yield表达式。https://docs.python.org/2/reference/simple_stmts.html#grammar-token-yield_stmt这种变化提出以允许用户将数据发送到发电机,就像一个会接受它。要发送数据,必须能够将其分配给某些内容,为此,语句将无效。

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库。 这是完整源代码的链接: 模块mspace


Grokking yield捷径

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

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

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

不要混淆你的Iterables,Iterators和Generators

首先, 迭代器协议 - 当你写

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

Python执行以下两个步骤:

  1. 获取mylist的迭代器:

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

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

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

    继续调用从步骤1返回的迭代器上的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()方法中的代码可能看起来非常复杂并且容易出错。 这里的发电机提供了一个简洁的解决方案


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

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

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


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


收益率是一个对象

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

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

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

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

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

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


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

另一个TL; DR

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

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

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

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


想一想:

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

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

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


所有伟大的答案,但新手有点困难。

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

作为类比,return并且yield是双胞胎。return意味着'回归和停止',而'产量'意味着'回归,但继续'

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

运行:

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

看,你只得到一个数字而不是它们的列表。return永远不会让你高兴,只需实施一次并退出。

  1. 来了 yield

替换returnyield

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

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

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

现在,你赢了所有的数字。

比较return哪一次运行和停止,yield运行您计划的时间。你可以理解returnreturn one of them,和yield作为return all of them。这叫做iterable

  1. 我们可以yield用另一个步骤重写语句return
In [15]: def num_list(n):
    ...:     result = []
    ...:     for i in range(n):
    ...:         result.append(i)
    ...:     return result

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

这是关键的核心yield

列表return输出和对象yield输出之间的区别是:

您将始终从列表对象中获取[0,1,2],但只能从“对象yield输出”中检索一次。因此,它有一个新的名称generator对象,如图所示Out[11]: <generator object num_list at 0x10327c990>

总之,作为一个隐喻它的隐喻:

  • return并且yield是双胞胎
  • list并且generator是双胞胎

要了解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... ”的所有内容都是可迭代的; listsstrings ,文件......

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

发电机

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

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

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

产量

yield是一个像return一样使用的关键字,除了函数将返回一个生成器。

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

这是一个无用的例子,但是当你知道你的函数将返回一组你只需要阅读一次的大量值时它会很方便。

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

然后,每次使用生成器时,都会运行代码。

现在困难的部分:

第一次调用从函数创建的生成器对象时,它将从头开始运行函数中的代码,直到达到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. 您可能有很多孩子,并且您不希望它们都存储在内存中。

它的工作原理是因为Python不关心方法的参数是否是列表。 Python期望iterables所以它将适用于字符串,列表,元组和生成器! 这叫做鸭子打字,这也是Python如此酷的原因之一。 但这是另一个故事,另一个问题......

你可以在这里停下来,或者阅读一下看看发电机的高级用途:

控制发电机的耗尽

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

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

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

Itertools,你最好的朋友

itertools模块包含操作iterables的特殊函数。 曾经希望复制发电机吗? 链两个发电机? 使用单行分组嵌套列表中的值? Map / Zip而不创建另一个列表?

然后只需import itertools

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

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

理解迭代的内在机制

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

在这篇文章中有关于for循环如何工作的更多信息


从编程的角度来看,迭代器实现为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
->

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

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

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

for line in getNextLines():
    doSomeThing(line)

执行控制转移问题

执行foryield时,执行控件将从getNextLines()传送到循环。因此,每次调用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并不是特别熟悉,但我相信它与C#的迭代器块相同,如果你熟悉它们的话。

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


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

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

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

许多人使用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()给出一个生成器。

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


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

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

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


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

yield from <expr>

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

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

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

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

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

避免协程与常规生成器混淆(今天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关键字并简单地返回一个生成器对象。似乎不是很有帮助。

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

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

未加引号的结果是印刷的。引用的结果是从中返回的结果yieldnext现在再打电话:

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


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

我想对一系列数字进行操作,但我不想因为创建该序列而烦恼我自己,我只想专注于我想要做的操作。所以,我做了以下事情:

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

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

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

for item in sequence:

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

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

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





coroutine