python-复盘-彻底理解生成器yield中next/sent函数区别

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()方法匹配起来啦.

你可能感兴趣的:(python-复盘-彻底理解生成器yield中next/sent函数区别)