Python生成器:了解执行顺序

2 投票
3 回答
1042 浏览
提问于 2025-04-17 22:21
 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
  1. power 这个函数使用了 yield,所以它是一个 generator(生成器)。只有当你调用 next() 时,函数里的代码才会被执行。
  2. 没错。adder 依赖于这个生成器,但它对正在遍历的数据一无所知(比如数据的大小)。
  3. 又对了。
  4. 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 指令后正好被中断,准备执行下一个。

撰写回答