Python:生成器表达式与yield

时间:2010-01-03 16:09:27

标签: python yield generator

在Python中,通过生成器表达式与使用 yield 语句创建生成器对象之间有什么区别吗?

使用 yield

def Generator(x, y):
    for i in xrange(x):
        for j in xrange(y):
            yield(i, j)

使用生成器表达式

def Generator(x, y):
    return ((i, j) for i in xrange(x) for j in xrange(y))

两个函数都返回生成元组的生成器对象,例如: (0,0),(0,1)等。

其中一种优势?想法?


谢谢大家!这些答案中有很多很好的信息和进一步的参考资料!

8 个答案:

答案 0 :(得分:68)

两者之间只有细微的差别。您可以使用dis模块自行检查此类事物。

编辑:我的第一个版本反编译了在交互式提示中在模块范围内创建的生成器表达式。这与OP的版本略有不同,它在一个函数中使用。我已修改此内容以匹配问题中的实际案例。

如下所示,“yield”生成器(第一种情况)在设置中有三条额外的指令,但是从第一个FOR_ITER它们只有一个方面不同:“yield”方法使用{ {1}}代替循环内的LOAD_FASTLOAD_DEREF"rather slower"而不是LOAD_DEREF,因此,对于足够大的LOAD_FAST(外部循环)值,它使“yield”版本比生成器表达式略快,因为x的值在每次传递时加载得稍快一些。对于较小的y值,由于设置代码的额外开销,它会稍微慢一些。

也许值得指出的是,生成器表达式通常在代码中内联使用,而不是像这样用函数包装它。这样可以消除一些设置开销,并使发生器表达式对于较小的循环值保持稍微快一些,即使x给出“yield”版本的优势也是如此。

在任何情况下,性能差异都不足以证明在一个或另一个之间作出决定。可读性更重要,因此请使用对手头情况最具可读性的方法。

LOAD_FAST

答案 1 :(得分:35)

在这个例子中,不是真的。但是yield可用于更复杂的构造 - for example它也可以接受来自调用者的值并因此修改流。阅读PEP 342了解更多详情(这是一项值得了解的有趣技巧)。

无论如何,最好的建议是使用更清楚的东西

P.S。以下是来自Dave Beazley的简单例程:

def grep(pattern):
    print "Looking for %s" % pattern
    while True:
        line = (yield)
        if pattern in line:
            print line,

# Example use
if __name__ == '__main__':
    g = grep("python")
    g.next()
    g.send("Yeah, but no, but yeah, but no")
    g.send("A series of tubes")
    g.send("python generators rock!")

答案 2 :(得分:17)

您可以将适合生成器表达式的简单循环类型没有区别。但是,yield可用于创建执行更复杂处理的生成器。以下是生成斐波纳契数列的简单示例:

>>> def fibgen():
...    a = b = 1
...    while 1:
...        yield a
...        a, b = b, a+b

>>> list(itertools.takewhile((lambda x: x<100), fibgen()))
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]

答案 3 :(得分:9)

在使用中,请注意生成器对象与生成器函数之间的区别。

与生成器函数相比,生成器对象只使用一次,每次再次调用它时都可以重用它,因为它返回一个新的生成器对象。

生成器表达式实际上通常使用“原始”,而不将它们包装在函数中,并返回生成器对象。

E.g:

def range_10_gen_func():
    x = 0
    while x < 10:
        yield x
        x = x + 1

print(list(range_10_gen_func()))
print(list(range_10_gen_func()))
print(list(range_10_gen_func()))

输出:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

比较略有不同的用法:

range_10_gen = range_10_gen_func()
print(list(range_10_gen))
print(list(range_10_gen))
print(list(range_10_gen))

输出:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[]
[]

与生成器表达式进行比较:

range_10_gen_expr = (x for x in range(10))
print(list(range_10_gen_expr))
print(list(range_10_gen_expr))
print(list(range_10_gen_expr))

也输出:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[]
[]

答案 4 :(得分:8)

如果表达式比嵌套循环更复杂,那么使用yield会很好。除此之外,您还可以返回特殊的第一个或特殊的最后一个值。考虑:

def Generator(x):
  for i in xrange(x):
    yield(i)
  yield(None)

答案 5 :(得分:5)

在考虑迭代器时,itertools模块:

  

...标准化一组核心的快速,内存有效的工具,这些工具本身或组合使用。它们共同组成了一个“迭代器代数”,可以在纯Python中简洁有效地构建专用工具。

为了提高效果,请考虑itertools.product(*iterables[, repeat])

  

输入迭代的笛卡尔积。

     

等效于生成器表达式中的嵌套for循环。例如,product(A, B)返回与((x,y) for x in A for y in B)相同的内容。

>>> import itertools
>>> def gen(x,y):
...     return itertools.product(xrange(x),xrange(y))
... 
>>> [t for t in gen(3,2)]
[(0, 0), (0, 1), (1, 0), (1, 1), (2, 0), (2, 1)]
>>> 

答案 6 :(得分:3)

是的,有区别。

对于生成器表达式if(myValue != myOtherValue || myValue != someOtherValue) { DoMyOneThing(); return; } ,当表达式创建时,将调用(x for var in expr)

使用iter(expr)def创建生成器时,如:

yield

def my_generator(): for var in expr: yield x g = my_generator() 尚未调用。只有在iter(expr)上进行迭代时才会调用它(可能根本不会被调用)。

以此迭代器为例:

g

此代码:

from __future__ import print_function


class CountDown(object):
    def __init__(self, n):
        self.n = n

    def __iter__(self):
        print("ITER")
        return self

    def __next__(self):
        if self.n == 0:
            raise StopIteration()
        self.n -= 1
        return self.n

    next = __next__  # for python2

,同时:

g1 = (i ** 2 for i in CountDown(3))  # immediately prints "ITER"
print("Go!")
for x in g1:
    print(x)

由于大多数迭代器在def my_generator(): for i in CountDown(3): yield i ** 2 g2 = my_generator() print("Go!") for x in g2: # "ITER" is only printed here print(x) 中没有做很多事情,因此很容易错过这种行为。一个真实世界的例子是Django的__iter__fetch data in __iter__QuerySet可能需要花费很多时间,而data = (f(x) for x in qs)后跟def g(): for x in qs: yield f(x)会立即返回。< / p>

有关详细信息和正式定义,请参阅PEP 289 -- Generator Expressions

答案 7 :(得分:0)

在某些尚未指出的情况下,有一些区别可能很重要。使用yield会阻止您将return用于除implicitly raising StopIteration (and coroutines related stuff)之外的其他内容。

这意味着此代码格式错误(并将其提供给解释程序将为您提供AttributeError):

class Tea:

    """With a cloud of milk, please"""

    def __init__(self, temperature):
        self.temperature = temperature

def mary_poppins_purse(tea_time=False):
    """I would like to make one thing clear: I never explain anything."""
    if tea_time:
        return Tea(355)
    else:
        for item in ['lamp', 'mirror', 'coat rack', 'tape measure', 'ficus']:
            yield item

print(mary_poppins_purse(True).temperature)

另一方面,这段代码就像一个魅力:

class Tea:

    """With a cloud of milk, please"""

    def __init__(self, temperature):
        self.temperature = temperature

def mary_poppins_purse(tea_time=False):
    """I would like to make one thing clear: I never explain anything."""
    if tea_time:
        return Tea(355)
    else:
        return (item for item in ['lamp', 'mirror', 'coat rack',
                                  'tape measure', 'ficus'])

print(mary_poppins_purse(True).temperature)