Python:生成器表达式与yield

csc*_*hol 82 python yield generator

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

使用产量:

def Generator(x, y):
    for i in xrange(x):
        for j in xrange(y):
            yield(i, j)
Run Code Online (Sandbox Code Playgroud)

使用生成器表达:

def Generator(x, y):
    return ((i, j) for i in xrange(x) for j in xrange(y))
Run Code Online (Sandbox Code Playgroud)

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

一个或另一个的任何优点?思考?


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

Pet*_*sen 70

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

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

正如您在下面所看到的,"yield"生成器(第一种情况)在设置中有三条额外的指令,但从第一种情况来看FOR_ITER它们只有一个方面不同:"yield"方法使用a LOAD_FAST代替LOAD_DEREF循环内部.的LOAD_DEREF"而较慢的"LOAD_FAST,所以它使得"产量"版本比为足够大的值,生成器表达式稍快x,因为值(外部环路)y对每个通稍快加载.对于较小的值,x由于设置代码的额外开销,它会稍微慢一些.

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

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

>>> def Generator(x, y):
...     for i in xrange(x):
...         for j in xrange(y):
...             yield(i, j)
...
>>> dis.dis(Generator)
  2           0 SETUP_LOOP              54 (to 57)
              3 LOAD_GLOBAL              0 (xrange)
              6 LOAD_FAST                0 (x)
              9 CALL_FUNCTION            1
             12 GET_ITER
        >>   13 FOR_ITER                40 (to 56)
             16 STORE_FAST               2 (i)

  3          19 SETUP_LOOP              31 (to 53)
             22 LOAD_GLOBAL              0 (xrange)
             25 LOAD_FAST                1 (y)
             28 CALL_FUNCTION            1
             31 GET_ITER
        >>   32 FOR_ITER                17 (to 52)
             35 STORE_FAST               3 (j)

  4          38 LOAD_FAST                2 (i)
             41 LOAD_FAST                3 (j)
             44 BUILD_TUPLE              2
             47 YIELD_VALUE
             48 POP_TOP
             49 JUMP_ABSOLUTE           32
        >>   52 POP_BLOCK
        >>   53 JUMP_ABSOLUTE           13
        >>   56 POP_BLOCK
        >>   57 LOAD_CONST               0 (None)
             60 RETURN_VALUE
>>> def Generator_expr(x, y):
...    return ((i, j) for i in xrange(x) for j in xrange(y))
...
>>> dis.dis(Generator_expr.func_code.co_consts[1])
  2           0 SETUP_LOOP              47 (to 50)
              3 LOAD_FAST                0 (.0)
        >>    6 FOR_ITER                40 (to 49)
              9 STORE_FAST               1 (i)
             12 SETUP_LOOP              31 (to 46)
             15 LOAD_GLOBAL              0 (xrange)
             18 LOAD_DEREF               0 (y)
             21 CALL_FUNCTION            1
             24 GET_ITER
        >>   25 FOR_ITER                17 (to 45)
             28 STORE_FAST               2 (j)
             31 LOAD_FAST                1 (i)
             34 LOAD_FAST                2 (j)
             37 BUILD_TUPLE              2
             40 YIELD_VALUE
             41 POP_TOP
             42 JUMP_ABSOLUTE           25
        >>   45 POP_BLOCK
        >>   46 JUMP_ABSOLUTE            6
        >>   49 POP_BLOCK
        >>   50 LOAD_CONST               0 (None)
             53 RETURN_VALUE
Run Code Online (Sandbox Code Playgroud)


Eli*_*sky 35

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

无论如何,最好的建议是使用更清楚的东西来满足您的需求.

PS这是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!")
Run Code Online (Sandbox Code Playgroud)

  • +1链接到David Beazley.他关于协同程序的演讲是我长期以来读过的最让人烦恼的事情.也许,就像他关于发电机的介绍一样有用,但令人惊讶. (8认同)

Dav*_*rby 18

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

>>> def fibgen():
...    a = b = 1
...    while True:
...        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]
Run Code Online (Sandbox Code Playgroud)

  • +1这是非常酷的...不能说我见过这样一个没有递归的短而甜蜜的实现. (4认同)

Cra*_*een 9

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

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

生成器表达式在实践中通常使用"原始",而不将它们包装在函数中,并返回生成器对象.

例如:

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()))
Run Code Online (Sandbox Code Playgroud)

哪个输出:

[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]
Run Code Online (Sandbox Code Playgroud)

比较略有不同的用法:

range_10_gen = range_10_gen_func()
print(list(range_10_gen))
print(list(range_10_gen))
print(list(range_10_gen))
Run Code Online (Sandbox Code Playgroud)

哪个输出:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[]
[]
Run Code Online (Sandbox Code Playgroud)

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

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))
Run Code Online (Sandbox Code Playgroud)

还输出:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[]
[]
Run Code Online (Sandbox Code Playgroud)


Tor*_*amo 8

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

def Generator(x):
  for i in xrange(x):
    yield(i)
  yield(None)
Run Code Online (Sandbox Code Playgroud)


Udi*_*Udi 6

是,有一点不同。

对于生成器表达式(x for var in expr)iter(expr)创建表达式时调用。

使用defyield创建生成器时,如下所示:

def my_generator():
    for var in expr:
        yield x

g = my_generator()
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

这段代码:

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

尽管:

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)
Run Code Online (Sandbox Code Playgroud)

由于大多数迭代器在 中没有做很多事情__iter__,很容易错过这种行为。一个真实的例子是 Django 的QuerySet,它获取数据__iter__并且data = (f(x) for x in qs)可能需要很多时间,而def g(): for x in qs: yield f(x)后跟data=g()将立即返回。

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


gim*_*mel 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)]
>>> 
Run Code Online (Sandbox Code Playgroud)