一、初识yield
yield英文单词的意思是生产、出产,在Python中yield是一个关键字,内部包含yield关键字的函数被称为生成器(generator)函数,这个函数的返回值是一个生成器对象。生成器是一种特殊的迭代器,它允许你按需迭代地生成值,而不是一次性生成所有值并将它们存储在内存中。这使得生成器非常适合处理大数据集或无限序列。
在生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值,在下一次执行 next() 方法时从当前位置继续运行。 有的文章解释说yield可以用来为一个函数返回值塞数据,这种解释有点道理但不全面。网上好多文章一开始提供的示例就类似与这样的代码:
看完之后好像理解了,好像又没理解,功能的确实现了而且没有return关键字,脑子里模模糊糊的有点概念,但不是很透亮的感觉,如果您也有这种感觉的话,请继续往下看,绝不会让你失望。
二、next()函数
为了深入了解yield的工作原理,需要配合next()函数,上图中 yield_func() 被调用后并没有立即执行,而是直到next(c)被调用后 yield_func() 才真正的开始执行,直到下一个yield。上图中只执行了一次next(c),根据打印的内容可知 yield_func() 执行到 [ yield_val01 = yield 5 ] 就停止了,因为之后的内容没有print出来。
上图中执行了两次next(c),与第一张图比较并结合打印的内容可知,第二次next(c)被调用后 yield_func() 中 [ yield_val01 = yield 5 ] 之后的内容继续执行,print成功打印了内容,但由于后面没有yield了,因此会抛出异常。 至此我们可知每执行一次 next() ,对应的生成器函数会从上一次执行到的位置的下一行开始继续执行直到遇到下一个yield为止(遇到的新yield会在本次next()中执行)。
三、send()函数
send() 和 next() 的作用很相似,区别是:send()可以传递yield的值,next()只能传递None。但第一次调用send()时只能是send(None),参数值不能是非None值,否则会报错TypeError,因为此时没有yield语句来接收这个值。
由上图打印的内容可知,第二次send()的参数值在 yield_func() 中被作为第一个yield的返回值,所以 yield_func() 中print输出内容中yield_val01的值被解析成 'parameterVal1',这也解释了为什么第一次调用send()时只能是send(None)。
由上图可知send()和next()一样,当上一次的执行位置之后没有yield,但仍要执行下一次send()时,就会抛出StopIteration异常。
四、generator(生成器)执行过程解密
1、最简单的创建一个生成器对象的方式,只要把一个列表生成式的 [] 改成 () ,就创建了一个generator:
2、第二种方法就是, 创建一个包含 yield 关键字的函数,生成器函数的返回值是一个生成器对象
由上图打印的内容可知:
- handle_list()中for循环之前的逻辑 print('[In handle_list start]') 只执行了一次
- handle_list()不是把datas全部处理完毕后return一个返回值,而是外层for循环需要取元素时就从handle_list()中拿一个,因为打印的内容中In、Out的日志是穿插着的
- yield之后的内容影响不了当前yield的返回值,所以yield之后最好没内容,如果有内容的话不能跟本次yield的返回值有关,可以跟下次yield的返回值有关
- 当无法从handle_list()获取到返回值时,外层for循环也就执行到尽头了
五、斐波拉切数列的普通实现与生成器实现对比
与第四节分析所得的结论一致,普通实现是先把datas全部处理完毕后return一个返回值,生成器是外层for循环需要取元素时就从fibo()中拿一个。
六、总结
各位读者朋友,看到这里你是否对python中yield的作用和执行过程有一个清晰的理解了呢?对这篇文章我是认真构思过的,措辞和截图都是经过思考自认为有对比性也能说明问题。
如果大家觉得有不清楚的地方,非常欢迎在评论区指出,如果读完之后觉得对自己有帮助,请点赞、评论支持下,谢谢大家。