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

时间:2014-06-04 09:20:17

标签: 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>

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

2 个答案:

答案 0 :(得分:3)

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

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

第二个函数只迭代迭代的元素,它将处理对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: 

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

如果您正在迭代不是生成器的简单迭代(如果yield from相当于一个循环+简单yield from s)。

从文体上讲,我更喜欢:

yield

在处理生成器时,而不是def h(): for el in range(4): yield el 一个genexp或使用return

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

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 + FOR_ITER。请注意,参数(YIELD_VALUE)是.0。该函数的字节码还包含对iter(range(4))LOAD_GLOBAL的调用,这些调用是查找GET_ITER并获取其可迭代所必需的。但是这个动作也必须由genexp执行,不是在代码内部,而是在调用之前。

答案 1 :(得分:1)

除了@ Bakuriu的好处 - 生成器函数实现send()throw()close()之外,我还遇到了另一个不同之处。有时,您有一些在达到yield语句之前发生的设置代码。如果该设置代码可以引发异常,那么生成器返回版本可能比生成器函数更可取,因为它会更快地引发异常。如,

def f(x):
    if x < 0:
        raise ValueError
    for i in range(4):
        yield i * i

def g(x):
    if x < 0:
        raise ValueError
    return (i * i for i in range(x))

print(list(f(4)))
print(list(g(4)))
f(-1)  # no exception until the iterator is consumed!
g(-1)

如果有人想要这两种行为,我认为以下是最好的:

def f(count):
    x = 0
    for i in range(count):
        x = yield i + (x or 0)

def protected_f(count):
    if count < 0:
        raise ValueError
    return f(count)

it = protected_f(10)
try:
    print(next(it))
    x = 0
    while True:
        x = it.send(x)
        print(x)
except StopIteration:
    pass

it = protected_f(-1)