【问题标题】:Difference between generator expression and generator function生成器表达式和生成器函数的区别
【发布时间】:2014-07-24 21:00:51
【问题描述】:

生成器表达式和生成器函数之间是否有任何区别(性能或其他方面)?

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>

我问是因为我想决定如何在使用这两者之间做出决定。有时生成器功能更清晰,然后选择就很明确了。我问的是那些代码清晰并不能使一个选择显而易见的时候。

【问题讨论】:

    标签: python python-3.x


    【解决方案1】:

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

    第一个,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: 
    

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

    仅当上述行为是您想要的时才使用yield from,如果您正在迭代一个不是生成器的简单可迭代对象(因此yield from相当于一个循环+简单yields)。

    从风格上来说我更喜欢:

    def h():
        for el in range(4):
            yield el
    

    而不是 return 使用 genexp 或使用 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_GLOBALGET_ITER 的调用,这些调用是查找range 并获取其可迭代对象所需的。然而,这个动作也必须由 genexp 执行,只是不是在它的代码中,而是在调用它之前。

    【讨论】:

    • 我添加了另一个答案仅供参考。
    【解决方案2】:

    除了@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)
    

    【讨论】:

      猜你喜欢
      • 2015-07-04
      • 2021-01-12
      • 2017-06-18
      • 2016-01-23
      • 2023-04-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-12-08
      相关资源
      最近更新 更多