第一次看到yield,懵逼,百度谷歌后,懵圈,直到看到了这篇文章 通俗易懂,部分内容其实有待考究,但好歹让人知道yield是什么了,把复杂的东西变简单这就是功力啊。
都说某个函数包含了yield,这意味着这个函数已经是一个生成器(generator)
那什么是生成器(generator)?
创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了,浪费可耻啊。
so,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator
简单说:python生成器是迭代器的一种,特殊之处在于一旦遇到yield都会被挂起且返回yield右边的值,之后调用next()函数和send()函数可唤醒下一次迭代,起点是 : 上次迭代遇到yield时后面的代码(下一行),开始执行
理论结合实践,看下面代码吧:
# _*_ coding: utf-8 _*_
__author__ = 'ing'
__date__ = '2019-07-18 16:28'
def foo():
print('starting...')
while True:
print("rolling...")
p = yield 5
print('p ---> ', p)
g = foo()
print("---------1---------")
print(next(g))
print("---------2---------")
print(next(g))
print("---------3---------")
暂停2分钟🤔下 输出结果
.
.
.
一休哥,回来了……
执行结果
---------1---------
starting...
rolling...
5
---------2---------
p ---> None
rolling...
5
---------3---------
来来,让我们单步调试:
1.程序开始执行后 g = foo(),此时日志只打印了
---------1---------
你应该会问不应该是先
starting...
么?
这说明foo函数并没有真的执行,由于foo函数中yield关键字,而是先得到一个生成器g
2.直到调用next方法,foo函数正式开始执行,先执行print 也就输出了starting...,然后进入while循环开始,打印了rolling...,这时候,关键的时刻到了,遇到了yield,此刻你把它看成‘return’,返回一个5之后,程序中断执行,并没有执行赋值给p的操作。到此,第一个next(g)执行结束:
starting...
rolling...
5
3.程序执行print("---------2---------")
4.再次执行print(next(g)) ,这个时候和上面不同的是这个时候是从上次next程序中断的地方开始执行的,也就是要执行p的赋值操作,要注意,这时的赋值操作右边是没有值的(因为‘return’出去了,并没有赋值操作给左边的传参数 #1),所以这个时候p赋值是None,所以日志显示
---------2---------
p ---> None
接着往下执行,进入到循环里面,执行
print("rolling...")
执行
p = yield 5
和上面的一样,return 5 然后中断,p 未被赋值,为None.
rolling...
5
最后执行print("---------3---------")结束
到这你该明白yield和return的关系和区别了,带yield的函数是一个生成器,而不是一个函数了,这个生成器有一个函数就是next函数,next就相当于“下一步”生成哪个数,这一次的next开始的地方是接着上一次的next挂起的地方执行的,所以调用next的时候,生成器并不会从foo函数的开始执行,只是接着上一步挂起的地方开始,然后遇到yield后,return出要生成的数,结束。
上面也算理解了yield,另外认识了next的作用,下面我们再认识下send,和next有什么区别。
修改下上面的程序,把第二个next替换成send方法,如下:
# _*_ coding: utf-8 _*_
__author__ = 'ing'
__date__ = '2019-07-18 16:28'
def foo():
print('开始--->')
while True:
print("循环--->")
p = yield 5
print('p ---> ', p)
g = foo()
print("---------1---------")
print(next(g))
print("---------2---------")
print(g.send(7))
print("---------3---------")
先执行看下效果:
---------1---------
开始--->
循环--->
5
---------2---------
p ---> 7
循环--->
5
---------3---------
对比下可以看出区别在于next()之后 ,p不再是None而是被赋值了。这是因为,send是发送一个参数给p的,因为上面讲到,'return'的时候,并没有把5赋值给p,下次执行的时候只好继续执行赋值操作,只好赋值为None了,而如果用send的话,开始执行的时候,先接着上一次return 5后执行,先把7赋值给了p,然后执行next的作用,遇到下一回的yield,return出结果后结束。
4.程序执行g.send(7),从yield关键字的下一行开始继续执行,send会把7这个值赋值给p变量
5.由于send方法包含next()方法,所以程序会继续往下执行,然后再次进入while循环
6.程序执行再次遇到yield关键字,yield会返回后面的值后,程序再次挂起,直到再次调用next方法或send方法。
,,,,
至此,想必应该大致理解了吧,不理解?再看一遍。。。
最后看一段代码
def foo(num):
print("starting...")
while num<10:
num=num+1
yield num
for n in foo(0):
print(n)
运行结果是什么呢?