Python生成器:了解执行顺序
21 def power(values):
22 print "power", values
23 for value in values:
24 print 'powering %s' % value
25 yield value
26
27 def adder(values):
28 print "adder", values
29 for value in values:
30 print 'adding to %s' % value
31 if value % 2 ==0:
32 yield value + 3
33 else:
34 yield value + 2
35
36 elements = [1, 4, 7, 9, 12, 19]
37 res = adder(power(elements))
38 print res.next()
39 print res.next()
40 print res.next()
41 print res.next()
输出:
adder <generator object power at 0x7fb6b9ee7910> <--- is this the stdout flush matter?
power [1, 4, 7, 9, 12, 19]
powering 1
adding to 1
3
powering 4
adding to 4
7
powering 7
adding to 7
9
powering 9
adding to 9
11
我在试着理解上面的代码。
1) 为什么在输出中,adder 先被打印出来,而 power [1, 4, 7, 9, 12, 19] 是后面的呢?
2) adder 不是在遍历元素,而是在遍历 power 生成器,对吧?
3) 请确认我对第 (1) 点的理解。也就是说,首先调用 adder,然后在 for value in values
中,adder 在查询 power 生成器,因此触发了打印 power 的那一行,然后才是打印 adder 的那一行?
4) 如果是这样,为什么打印语句 power [1, 4, 7, 9, 12, 19]
并没有和 powering <$>
的打印语句每次都一起被调用呢?
3 个回答
3
生成器里的代码不会立即执行,只有在调用 next 的时候才会运行:
def gen():
print "called"
yield 3.14
g = gen() # nothing is printed
g.next() # print "called" and return 3.14
在这里,for 循环的作用就是在你的代码中调用 next,这个过程发生在打印 adder 之后:
g = gen()
print 'adder' # prints adder
for i in g: # prints called (as inside generator, *before* yields)
print i # prints 3.14
4
生成器函数在第一次调用 __next__
时才会开始执行。例如:
>>> def gen():
... print 'starting execution'
... for i in range(10): yield i
...
>>> itr = gen()
>>> next(itr)
starting execution
0
所以,回答你的第一个问题,“为什么在 power 之前打印了 adder”,当你执行 adder(power(elements))
时,生成器 power(elements)
首先被创建,但这个生成器的执行不会开始,直到在 adder()
中第一次进行 for value in values
的循环。
4
power
这个函数使用了yield
,所以它是一个generator
(生成器)。只有当你调用next()
时,函数里的代码才会被执行。- 没错。
adder
依赖于这个生成器,但它对正在遍历的数据一无所知(比如数据的大小)。 - 又对了。
yield
是一个特别的指令。它不会让生成器函数(power
)直接返回。相反,它会提供一个值,然后暂停执行,直到下次调用next()
。这时,执行会从暂停的地方继续,也就是在循环内部。
编辑
下面是 yield
暂停的示意:
def gene():
print 'first!'
yield 1
print 'second!'
yield 2
g = gene()
g.next()
# first!
# 1
g.next()
# second!
# 2
正如你所看到的,生成器在 yield
指令后正好被中断,准备执行下一个。