yield 是 Python 中的一个关键字, 这个关键字比较特殊, 用于在任何表达式前, 但它不仅会对其后的表达式有影响, 对整个函数上下文都有影响. 实际上, 凡是在函数体中出现了 yield 关键字, Python 都会对此函数特殊处理, 调用这个函数不再返回值, 而是一个生成器对象.比如
def f():
yield 1
g = f()
print type(g)
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 来观察. 这样的话就再加上一句 nextdef 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.不过话说回来, 能手动控制让执行到一半的函数挂起, 等到之后再执行, 还是挺有意思的. (完)