yield
是 Python 中的一个关键字, 这个关键字比较特殊, 用于在任何表达式前, 但它不仅会对其后的表达式有影响, 对整个函数上下文都有影响. 实际上, 凡是在函数体中出现了 yield
关键字, Python 都会对此函数特殊处理, 调用这个函数不再返回值, 而是一个生成器对象.比如
def f():
yield 1
g = f()
print type(g)
<type 'generator'>
next
函数def f():
yield 1
g = f()
print g.next()
yield
, 或者函数的执行过程中反复路过某个 yield
, 那么 next
每次调用会得到下一个产生的值, 比如def f():
yield 1
yield 2
g = f()
print g.next(),
print g.next()
1 2
. 或如下的循环def f():
for x in range(3):
yield x * 2
g = f()
print g.next(),
print g.next(),
print g.next()
next
函数作为返回值... 当然这样来说是不准确地, 如果上面 for
循环如果跑得太快, 那样会疾速产生值导致 next
函数应接不暇, 这样会有诡异的同步问题. 所以正确的语义应该是- 当生成器函数执行到包含
yield
的表达式时, 函数挂起, 并将yield
之后的表达式作为返回值传递给next
函数调用 - 下一次
next
函数调用又会驱动该生成器的函数体继续执行此后的语句, 直到遇见下一个yield
再次挂起并生成一个值交给next
- 如果某次
next
调用驱动了生成器继续执行, 而此后函数正常结束的话, 那么不会有任何值传递给next
函数, 同时, 生成器会抛出StopIteration
异常
def f():
yield 1
g = f()
print g.next(),
print g.next()
return
, 它必须是一句空返回, 即 return
之后不允许跟任何表达式, 这也是限制之一.从下面这个例子可以更好地窥探生成器的执行模式
def f():
print 'a'
yield 1
print 'b'
yield 2
print 'c'
g = f()
print 'start'
print g.next()
print 'next'
print g.next()
print 'end'
start
a
1
next
b
2
end
next
之后, f
函数体的执行就挂起了, 等着下一次调用 next
. 另外, f
中最后一句应该输出的 c
并没有输出, 因为第二次生成器给出了 2
这个值之后就一直处于挂起状态.如果要大慈大悲地让最后一句也执行, 那么需要再加一句
next
调用def f():
print 'a'
yield 1
print 'b'
yield 2
print 'c'
g = f()
print 'start'
print g.next()
print 'next'
print g.next()
print 'end'
g.next()
c
输出之后, 还会带出个异常. 所以这东西有时候并不那么好用.那它有何用?
一个典型的非它不可的场景是模拟无穷列表, 比如
def f():
n = 0
while True:
yield 'loli #' + str(n)
n = n + 1
g = f()
print g.next()
print g.next()
# ...
yield
来搞定.然而
yield
这东西并不是一个 return
的替代品, 它更像一个前置单目运算操作符, 并且因此它可以出现在表达式的任何位置. 如def f():
x = 'lo'
y = 'li'
z = (yield x) + (yield y)
print z
g = f()
print g.next(),
print g.next()
lo li
这个字符串, 但留下了一些疑点, 上面的含 yield
的表达式如 (yield x)
它的取值是多少? 要知道这个, 就得让代码继续运行下去然后输出变量 z
来观察. 这样的话就再加上一句 next
def f():
x = 'lo'
y = 'li'
z = (yield x) + (yield y)
print z
g = f()
print g.next(),
print g.next()
g.next()
StopIteration
, 而是TypeError: unsupported operand type(s) for +: 'NoneType' and 'NoneType'
(yield x)
与 (yield y)
这两个表达式的值都是 None
了.原因是, 调用
next
函数只能从生成器中获取一个值, 而不能将一个值传入生成器中. 而 yield
这货不仅是用来从生成器传出一个值的工具, 它还是一个向生成器中 yield
所在表达式中传入值的通道. 要利用这样的通道需要调用生成器的 send
函数, 如def f():
x = 'lo'
y = 'li'
z = (yield x) + (yield y)
print z
g = f()
print g.next()
print g.send('kagami ')
g.send('tukasa')
StopIteration
. 并且, 在此之前可以观察到 z
的值, 为通过两次 send
函数传入的字符串相连的结果.而更准确地说,
next()
调用相当于一次 send(None)
调用, 所以在之前那个例子中会出现尝试将两个 None
值加和的错误. 因此上述代码又等价于def f():
x = 'lo'
y = 'li'
z = (yield x) + (yield y)
print z
g = f()
print g.send(None)
print g.send('kagami ')
g.send('tukasa')
f
明明内部只有两个 yield
, 却有三次 send
; 而第一次 send
的实参值必须是 None
(否则挂), 并且最后一次 send
之后必然会出现一次 StopIteration
. 这看起来简直就是 Python 生成器设计上的 bug.不过话说回来, 能手动控制让执行到一半的函数挂起, 等到之后再执行, 还是挺有意思的. (完)