为什么python生成器需要yield?

Why yield is required for python generator?

在阅读了answer1和answer2之后,yield的目的看起来仍然不清楚。

在第一种情况下,使用下面的函数,

1
2
3
4
def createGenerator():
   mylist = range(3)
   for i in mylist:
      yield i*i

调用下面的createGenerator

埃多克斯1〔2〕

应返回类型为collections.abc.Generator的对象(如(x*x for x in range(3)),是-a collections.abc.Iteratorcollections.abc.Iterable

迭代myGenerator对象,得到第一个值(0)。

埃多克斯1〔9〕

实际上会使createGenerator函数的for循环在内部调用__iter__(myGenerator)并检索collections.abc.Iterator类型的对象(obj表示),然后调用__next__(obj)获取第一个值(0然后使用yield关键字暂停for循环

如果上述理解是正确的,那么,

然后,执行以下语法(第二种情况),

1
2
3
4
def createGenerator():
   return (x*x for x in range(3))
myGen = createGenerator() # returns collections.abc.Generator type object
next(myGen) # next() must internally  invoke __next__(__iter__(myGen)) to provide first value(0) and no need to pause

难道不足以达到同样的目的(如上所述)并且看起来更具可读性吗?这两种语法不是都有效吗?如果是,那么我什么时候应该使用yield关键字?有没有一种情况下,yield可能是必须使用的?


尝试在没有yield的情况下进行此操作。

1
2
3
4
5
6
7
8
9
10
11
def func():
    x = 1
    while 1:
        y = yield x
        x += y


f = func()
f.next()  # Returns 1
f.send(3)  # returns 4
f.send(10)  # returns 14

发电机有两个重要特点:

  • 发电机某种状态(x的值)。由于这种状态,该生成器最终可以返回任意数量的结果,而无需使用大量内存。

  • 由于状态和yield,我们可以向生成器提供用于计算下一个输出的信息。当我们调用send时,该值被分配给y

  • 我认为没有yield这是不可能的。也就是说,我非常肯定,用生成器函数可以做的任何事情也可以用类来完成。

    下面是一个类的例子,它执行完全相同的操作(python 2语法):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class MyGenerator(object):
        def __init__(self):
            self.x = 1

        def next(self):
            return self.x

        def send(self, y):
            self.x += y
            return self.next()

    我没有实现__iter__,但很明显这应该是如何工作的。


    把收益看成是"懒惰的回报"。在第二个示例中,函数不返回"值生成器",而是返回完全评估的值列表。这可能完全可以接受,具体取决于用例。当处理大量流式数据时,或者处理不立即可用的数据时(考虑异步操作),yield非常有用。


    已经有了一个很好的答案,即能够将send数据转换成一个有收益的生成器。考虑到可读性方面的考虑,虽然非常简单,但直接的转换可以作为生成器表达式更易于阅读:

    1
    (x + 1 for x in iterable if x%2 == 1)

    使用完整的生成器定义,某些操作更容易阅读和理解。在某些情况下,如果要适应生成器表达式,请尝试以下操作:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    >>> x = ['arbitrarily', ['nested', ['data'], 'can', [['be'], 'hard'], 'to'], 'reach']
    >>> def flatten_list_of_list(lol):
    ...     for l in lol:
    ...         if isinstance(l, list):
    ...             yield from flatten_list_of_list(l)
    ...         else:
    ...             yield l
    ...
    >>> list(flatten_list_of_list(x))
    ['arbitrarily', 'nested', 'data', 'can', 'be', 'hard', 'to', 'reach']

    当然,您可以使用lambda来实现递归,但这将是一个不可读的混乱。现在假设我有一些任意嵌套的数据结构,涉及到listdict,并且我有逻辑来处理这两种情况……你明白我的意思了。


    生成器的功能和生成器的理解基本相同-都会生成生成器对象:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    In [540]: def createGenerator(n):
         ...:     mylist = range(n)
         ...:     for i in mylist:
         ...:         yield i*i
         ...:        
    In [541]: g = createGenerator(3)
    In [542]: g
    Out[542]: <generator object createGenerator at 0xa6b2180c>

    In [545]: gl = (i*i for i in range(3))
    In [546]: gl
    Out[546]: <generator object <genexpr> at 0xa6bbbd7c>

    In [547]: list(g)
    Out[547]: [0, 1, 4]
    In [548]: list(gl)
    Out[548]: [0, 1, 4]

    ggl具有相同的属性;产生相同的值;以相同的方式耗尽。

    就像列表理解一样,在显式循环中,有一些事情是你无法理解的。但如果理解起作用,就用它。生成器是在2.2版左右添加到python的。生成器的理解是更新的(并且可能使用相同的底层机制)。

    在py3-range或py2-xrange中,一次生成一个值,而不是整个列表。它是一个range对象,不是一个生成器,但工作方式大致相同。Py3以其他方式对此进行了扩展,例如字典keysmap。有时这是一种方便,有时我忘了把它们包在list()里。

    yield可以更为详细,允许为来电者提供"反馈"。例如

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    In [564]: def foo(n):
         ...:     i = 0
         ...:     while i<n:
         ...:         x = yield i*i
         ...:         if x is None:
         ...:             i += 1
         ...:         else:
         ...:             i = x
         ...:            

    In [576]: f = foo(3)
    In [577]: next(f)
    Out[577]: 0
    In [578]: f.send(-3)    # reset the counter
    Out[578]: 9
    In [579]: list(f)
    Out[579]: [4, 1, 0, 1, 4]

    我认为生成器操作的方式是创建用代码和初始状态初始化对象。next()运行到yield并返回该值。下一个next()让它再次旋转,直到它碰到yield,依此类推,直到它碰到stop iteration状态。所以它是一个保持内部状态的函数,可以用nextfor迭代反复调用。使用sendyield from等工具,generators可以更加复杂。

    通常一个函数运行到完成,然后返回。对函数的下一个调用独立于第一个调用,除非使用全局或容易出错的默认值。

    https://www.python.org/dev/peps/pep-0289/是用于生成器表达式的pep,来自v 2.4。

    This PEP introduces generator expressions as a high performance, memory efficient generalization of list comprehensions [1] and generators [2] .

    https://www.python.org/dev/peps/pep-0255/pep用于发电机,v.2.2