为什么 python 生成器需要 yield?

Why yield is required for python generator?

阅读answer1 and answer2后,yield的目的仍然不清楚。


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

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

在下面调用 createGenerator 时,

myGenerator = createGenerator()

应该return类型collections.abc.Generator类型的对象(如(x*x for x in range(3))),is-a collections.abc.Iterator & collections.abc.Iterable

遍历 myGenerator 对象并获取第一个值(0),

next(myGenerator)

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

暂停 for 循环

如果这个理解(以上)是正确的,那么,

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

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

的情况下执行此操作
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

生成器有两个重要特性:

  1. 生成器的某个状态(x的值)。由于这种状态,此生成器最终可以 return 任意数量的结果,而无需使用大量内存。

  2. 由于状态和 yield,我们可以为生成器提供用于计算下一个输出的信息。当我们调用 send.

  3. 时,该值被分配给 y

如果没有 yield,我认为这是不可能的。 也就是说,我很确定你可以用生成器函数做的任何事情也可以用 class.

来完成

这是一个 class 的例子,它做的事情完全相同(python 2 语法):

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__ 但很明显它应该如何工作。

将收益率视为 "lazy return"。在你的第二个例子中,你的函数不是 return a "generator of values",而是一个完全评估的值列表。根据用例,这可能是完全可以接受的。 Yield 在处理大批量流式数据或处理不是立即可用的数据(想想异步操作)时很有用。

生成器函数和生成器理解基本相同——都生成生成器对象:

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 可以更详细,允许调用者 'feedback'。例如

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 和 returns 该值。下一个 next() 让它再次旋转,直到它达到 yield,依此类推,直到它达到 stop iteration 条件。所以它是一个维护内部状态的函数,可以用 nextfor 迭代重复调用。使用 sendyield from 等等 generators 可以更加复杂。

通常是一个函数 运行s 直到完成,并且 returns。对该函数的下一次调用与第一次调用无关——除非您使用全局变量或容易出错的默认值。


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

关于将数据 send 放入带有 yield 的生成器的能力,已经有了很好的答案。关于可读性方面的考虑,虽然简单、直接的转换可以作为生成器表达式更具可读性:

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

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

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

当然,您可以使用 lambdas 来破解适合单行的解决方案以实现递归,但这将是一团乱麻。现在假设我有一些涉及 listdict 的任意嵌套数据结构,并且我有处理这两种情况的逻辑......你明白我的意思了。