生成器表达式与生成器函数的区别

Nei*_*l G 7 python python-3.x

生成器表达式和生成器函数之间是否存在性能差异?

In [1]: def f():
   ...:     yield from range(4)
   ...:

In [2]: def g():
   ...:     return (i for i in range(4))
   ...:

In [3]: f()
Out[3]: <generator object f at 0x109902550>

In [4]: list(f())
Out[4]: [0, 1, 2, 3]

In [5]: list(g())
Out[5]: [0, 1, 2, 3]

In [6]: g()
Out[6]: <generator object <genexpr> at 0x1099056e0>
Run Code Online (Sandbox Code Playgroud)

我问,因为我想决定如何决定如何使用这两者.有时发电机功能更清晰,然后选择很明确.我问的是代码清晰度没有明显的选择.

Bak*_*riu 4

在一般情况下,您提供的函数具有完全不同的语义。

第一个, with yield from,将控制传递给可迭代对象。这意味着对迭代send()throw()迭代期间的调用将由可迭代对象处理,而不是由您定义的函数处理。

第二个函数仅迭代可迭代的元素,并且它将处理对send()和 的所有调用throw()。要查看差异,请检查以下代码:

In [8]: def action():
   ...:     try:
   ...:         for el in range(4):
   ...:             yield el
   ...:     except ValueError:
   ...:         yield -1
   ...:         

In [9]: def f():
   ...:     yield from action()
   ...:     

In [10]: def g():
    ...:     return (el for el in action())
    ...: 

In [11]: x = f()

In [12]: next(x)
Out[12]: 0

In [13]: x.throw(ValueError())
Out[13]: -1

In [14]: next(x)
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-14-5e4e57af3a97> in <module>()
----> 1 next(x)

StopIteration: 

In [15]: x = g()

In [16]: next(x)
Out[16]: 0

In [17]: x.throw(ValueError())
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-17-1006c792356f> in <module>()
----> 1 x.throw(ValueError())

<ipython-input-10-f156e9011f2f> in <genexpr>(.0)
      1 def g():
----> 2     return (el for el in action())
      3 

ValueError: 
Run Code Online (Sandbox Code Playgroud)

事实上,由于这个原因,yield from可能比 genexp 具有更高的开销,尽管它可能无关紧要。

yield from仅当上述行为是您想要的或者您正在迭代不是生成器的简单迭代时才使用(因此yield from相当于循环 + 简单yields)。

从风格上来说我更喜欢:

def h():
    for el in range(4):
        yield el
Run Code Online (Sandbox Code Playgroud)

而不是在处理生成器时return使用 genexp 或 using 。yield from

事实上,生成器用于执行迭代的代码几乎与上述函数相同:

In [22]: dis.dis((i for i in range(4)).gi_code)
  1           0 LOAD_FAST                0 (.0)
        >>    3 FOR_ITER                11 (to 17)
              6 STORE_FAST               1 (i)
              9 LOAD_FAST                1 (i)
             12 YIELD_VALUE
             13 POP_TOP
             14 JUMP_ABSOLUTE            3
        >>   17 LOAD_CONST               0 (None)
             20 RETURN_VALUE
Run Code Online (Sandbox Code Playgroud)

正如你所看到的,它做了一个FOR_ITER+ YIELD_VALUE。请注意,参数 ( .0) 是iter(range(4))。函数的字节码还包含查找和获取其可迭代对象所需的对LOAD_GLOBAL和的调用。然而,此操作也必须由 genexp 执行,只是不是在其代码内部而是在调用它之前执行。GET_ITERrange