简述 yield 和 yield from
yield
函数可以看成是一堆指令的集合。在函数中加入yield可以把一个函数变成一个generator,虽然调用的方式不一样了,但是其实现的功能和原来的函数基本是一样的。
而yield在这其中的作用是,把运行中的函数进行了一个保存退出(也就是中断)。然后把线程的的控制权从函数手里交换到我们手里(这样说很牵强,因为程序都是我们写的哈哈), 我们可以在适当的时机在继续运行该函数。
函数运行过程中的中断操作在单线程中可以用来实现一些很神奇的功能。尤其是在网络i/o操作中。
test():
request_baidu_server(baidu_data)
yield
print(baidu_data)
request_baidu_server(baidu_data)
去请求百度需要走很长的网络线路,三路握手,数据传递。这在cpu的感官里这就是等待数年的操作。我们的程序无法继续运行,难道我们就这样让cpu等待?当然不
所以调用yield把线程的控制权交出来,然后我们让线程去做一些其他的事情,比如再入请求新浪服务器。又要等待?ok,我们继续,就一瞬间,我们同时请求了上千台服务器的数据。这难道还不够神奇吗?
稍微总结一下yield,通过其可以让我们程序员实现对线程的调配,从而更加充分的利用的们的cpu。
上面的程序只是一个雏形,但是由于yield的功能比较简单,所以不赘述。至少要了解一下send()操作才能继续往下看。
yield from
一个函数不可能写几千行,通常都是 a调用b,b调用c, c调用a。这样会增加代码的可读性,复用性。
现在思考假如b()调用c(),c()中存在yield时我们应该如何处理?
我们可能需要这么处理
def b():
gen = c()
r = gen.send(None)
print(r)
r = gen.send(None)
# 或者用for gen in c()来进行生成器的运行,但是这样已经很麻烦了。
def c():
yield 1
yield 2
yield 3
...
鉴于在生成器中调用生成器比较麻烦,所以有了yield from语法。用于简化嵌套调用生成器的语法复杂度,下面是一个示例
def b():
r = yield from c()
print(r) # 4
def c():
r = yield 1
print(r) # 哈哈
return 4
gen = b()
gen.send(None) # 1
gen.send('哈哈') # 2
当我们第一次调用send时,运行到语句 yield from c()
这条语句在我理解就是
gc = c(),res = gc.send(None),yield res
执行了这三条语句.
但是,其中第二条指令gc.send()
执行时,线程的控制权从b函数转移到了c函数。 所以yield from c()
可以理解为保存并中断当前函数,然后把线程的控制权交给c函数。
此时线程执行c函数,函数执行了一条r = yield 1
进行了保存退出,此时线程的控制权又再一次回到了我们手上。也就是gen.send(None)
返回了结果1。
此时线程的控制权在我们手里,然后我们调用gen.send('哈哈')
,又把线程的控制权交还给了c函数,线程来到c函数刚刚的退出点继续往下执行。
根据语法定义,send('哈哈')
的参数将会作为yield的返回值。也就是r = yield 1
中的r
会收到字符串 '哈哈'。我们现在透过了b函数,直接和c函数进行了交互。我想这就是yield from最大的作用了!
我们继续往下走,此时c函数 return 4
这又会产生什么效果呢?你可以猜测一下。
说一下我的理解,c函数现在执行完毕,自然是回到调用处。也即是r = yield from c()
,现在重新审视一下这条语句。b函数执行过程中需要c的结果,否则没有办法继续往下执行,所以把线程控制权交给了c。b经过了数年的等待终于等到了c的执行结果,也就是return 4
中的4。此时4的值会赋给r。然后函数继续往下执行。这就和我们再普通函数a()中调用b()一样,只不过是在生成器调用生成器需要用到特殊的写法而已。
为什么需要yield from, 已经yield from的作用我想应该理解了吧~
补充一句, 很多原理性的东西我其实不确定,以上只是我的感性的理解。比如4的值是如何赋给r我并不知道。我们是否是直接越过b函数和c函数进行交互,我也不确定。我目前没有能力进行更深层次的了解。
再啰嗦一下~。上面的程序最后三行就像一个主控器一样,调控b(),c()函数的运行,并且远不止于此。我们可以运行一会c,运行一会b。运行一会d,e,f,g等等。 你也许会疑问这有什么意义?
在cpu密集型运算的程序中,这一点意义都没有。 但是在i/o密集型运算中,这样做的意义是非凡的。
当我们遇到需要长时间i/o等待时,就把线程的控制权交给下一个函数或者说http请求,长时间的i/o返回结果时, 我们我们在回到原有的程序。
这在cpu眼里是按顺序执行的,但在我们眼里确是单个线程同时处理着上千个请求,因为cpu的处理速度非常快。 i/o操作不止于网络i/o,还有文件读写i/o,数据库读写i/o。
yield实现的功能就像一块肥肉一样,没有那种语言想要放弃这种功能。
python在yield的基础上实现了asyncio模块。下一节我浅谈一下asyncio这个模块。
本作品采用《CC 协议》,转载必须注明作者和本文链接