yield与生成器
def func(n):
for i in range(0, n):
print('func: ', i)
yield i
f = func(10)
print(func(10)) # 生成器是无法执行的
如何调用生成器?
import time
def func(n):
for i in range(0, n):
print('func: ', i)
yield i
f = func(10)
while True:
print(next(f))
time.sleep(1)
结果如下:
func: 0
0
func: 1
1
func: 2
2
...........
func: 8
8
func: 9
9
Traceback (most recent call last):
File "C:/Users/mingC/PycharmProjects/pro_test/Demo/Demo4.py", line 9, in
print(next(f))
StopIteration
再看一个例子
In [62]: def play():
...: try:
...: yield 1 # 程序每执行一次暂停,下次从断点处执行
...: yield 2
...: yield 3
...: finally:
...: yield 0
结果:
In [68]: for v in play():
...: print (v)
...:
1
2
3
0
生成器函数在每次暂停执行时,函数体内的所有变量都将被封存(freeze)在生成器中,并将在恢复执行时还原,并且类似于闭包,即使是同一个生成器函数返回的生成器,封存的变量也是互相独立的。
send
send是除next外另一个恢复生成器的方法。Python 2.5中,yield语句变成了yield表达式,这意味着yield现在可以有一个值,而这个值就是在生成器的send方法被调用从而恢复执行时,调用send方法的参数。
>>>def repeater():
... n = 0
... while True:
... n = (yield n)
>>> r = repeater()
>>>r.next()
0 # 结果
>>>r.send(10)
10
再看一个例子(经典):
import time
def func(n):
for i in range(0, n): # 在yield中我们知道程序遇到yield就会暂停,return是彻底停止。这里的 for 循环相当于形成一个贪吃蛇♻️的模型,不断从下方的暂停点和启动点循环,每次到暂停点,i会+1,且值会给到send,若上一个启动点是send启动的话。
arg = yield i # yield i 是停止代码执行暂停点,arg = yield 是代码启动点,注意理解 !
print('func:', arg) # 回到yield i 这个终止点的时候,此时已经算是又一次的循环,因为开始从arg = yield时并未循环,所以此时应该 i +1 ,所以结果里的main2 相对main1 要大一个 1
f = func(10)
while True:
print('main1:', next(f)) # next每次从arg = yield的启动点开始,由于它不像send带value,所以,此时arg是None,即不存在。所以每次打印都是None
print('main2:', f.send(100))
time.sleep(1)
输出结果:
main1: 0
func: 100
main2: 1
func: None
main1: 2
func: 100
main2: 3
func: None
main1: 4
func: 100
main2: 5
func: None
main1: 6
func: 100
main2: 7
func: None
main1: 8
func: 100
main2: 9
Traceback (most recent call last):
func: None # 9之后,next(f)它是从arg = yield开始执行的,所以虽然range已经结束,但它还是顽强的把None给打印出来,再回到yield i 的时候才彻底结束。
File "C:/Users/mingC/PycharmProjects/pro_test/Demo/Demo4.py", line 9, in
print('main:', next(f))
StopIteration
再来看一段yield
更复杂的用法
>>> def echo(value=None):
... while 1:
... value = yield value
... print("The value is", value)
... if value:
... value += 1
...
>>> g = echo(1)
>>> next(g) # 这是第一次启动生成器,所以按顺序执行,到yield value这句时程序停止执行,但此时yield会把停止时产生的值给生成器echo,即对应next(g)的值,所以第一次是1
1 # 注:value = yield value可以看成 value = yield 和 yield value,左边是启动点,右边是暂停点
>>> g.send(2) # 第二次执行,send 把2给了启动点 value = yield,所以有了value is 2,接着呢,+1变成3,此时这个值要给到循环给到暂停点yield value,程序暂停,此时yield value的结果是3,3要回传给g.send(2)
The value is 2 # send的特点是给一个value值,这个值等同于yield后的结果值,也就是启动点的值,相当于发了一个消息。转一圈了,等到暂停点 yield value时,send要接收这个yield value的结果。
3
>>> g.send(5)
The value is 5
6
>>> next(g)
The value is None
解释,口语化讲解参考代码后备注:
上述代码既有yield value
的形式,又有value = yield
形式,看起来有点复杂.但以yield分离代码进行解读,就不太难了.第一次调用next()
方法,执行到yield value
表达式,保存上下文环境暂停返回1.第二次调用send(value)
方法,从value = yield
开始,打印,再次遇到yield value暂停返回.后续的调用send(value)
或next()
都不外如是.
但是,这里就引出了另一个问题,yield作为一个暂停恢复的点,代码从yield处恢复,又在下一个yield处暂停.可见,在一次next()
(非首次)或send(value)
调用过程中,实际上存在2个yield,一个作为恢复点的yield与一个作为暂停点的yield.因此,也就有2个yield表达式.send(value)方法是将值传给恢复点yield;调用next()表达式的值时,其恢复点yield的值总是为None,而将暂停点的yield表达式的值返回.为方便记忆,你可以将此处的恢复点记作当前的(current),而将暂停点记作下一次的(next)
,这样就与next()
方法匹配起来啦.