您的位置:首页 > 编程语言 > Python开发

人生苦短我用python[0x02] yield浅析

2017-05-27 00:00 169 查看
1.yield介绍
先来几个单词翻译,根据词霸的翻译有以下截图







一开始学习yield确实有点懵,yield在python,java,c#等语言都有,一开始在国内外各大网站都找了相关的资料学习这个yield,通常能找到的文章个人感觉都说得差不多一个意思,通过编写程序实践了一下,这篇文章试图以自己理解的角度来讲解python的yield,水平有限,难免有误,欢迎各路大神指正。
在解析yield之前,我们先看一段代码
#!/usr/bin/env python

#test带上了yield后就变成了个生成器了
#参数i用于标识当前的生成器
#生成器每循环一次就把i值输出来,同时i++
def test(i):
index = i
while True:
print i
ret = yield
if ret == 1:
#如果外部通过调用send传入来的参数是1的话
#代表要退出循环结束这个生成器了
break
i = i + 1
print "%d exit"%(index)

#记录生成器列表
test_list = []
#我们先创建3个生成器,分别传入参数100,200,300作为标识
for i in range(1, 4):
test_list.append(test(i*100))

#主循环
while True:
#等待键盘输入
t = raw_input("input:");
if t == 'q':
#如果输入的是q,就调用close函数结束生成器然后程序退出
for t in test_list:
t.close()
break
elif t == 'c':
#如果输入的是c,就调用send函数发送1通知生成器推出运行然后程序退出
for t in test_list:
try:
t.send(1)
except:
pass
break
else:
#如果输入的是其他则运行生成器
#当生成器运行到yield的时候会暂停,
#等待next或者send的调度
for t in test_list:
t.next()
运行程序第一次的时候,按3次回车,test生成器分别都对自己的i值进行了++然后输出来,运行到yield语句的时候就会退出来一直到调用next,send等函数进行调度,最后我们输入c,就调用send(这里需要捕获异常,因为使用send来通知生成器结束运行会抛出一个异常)函数发送了1通知3个生成器退出循环,生成器也结束了。
运行程序第二次的时候,按2次回车,输出结果跟第一次一样,最后我们输入q,程序则调用close结束3个生成器的运行。



看完了代码和运行结果,我们再来讲解生成器,yield这些概念。test_list.append(test(i100)) 我们看到,test(i00)实际上是不会触发test函数运行的,它返回了一个生成器,个人理解这个生成器跟 协程 或者 闭包 有点类似,test生成器有属于自己的运行上下文,里面可以调用yield停止运行,外部可以通过next,send继续运行,生成器一开始的时候并不会运行一直到调用next或者send才触发生成器的第一次运行,运行到yield语句会暂停,等待下一次的next或者send触发调度,这部分功能跟 协程 非常相似。
根据上面讲的和调试结果,个人觉得generators如果要用中文来解释的话,大概就是一个代码段,这个代码段有自己的运行空间,这个代码段可以由外部触发运行,终止,代码段里面可以调用yield来暂停运行。这个yield就比较好解释了,就是暂停当前代码段的运行,或者挂起,等待外部触发调度继续运行的意思。其实这个就是协程的概念了。
我们可以利用yield的特性,实现一个单线程并发的tcp网络处理模型,我们把每个tcp连接都建立一个生成器比如叫client,把tcp设置成非阻塞noblocking,循环读取tcp接收缓存区,当接收缓存区没有数据时马上调用yield暂停这个连接生成器的运行,让其它tcp连接生成器运行。外部程序有个大循环,每次都用next调度所有生成器去查看自己tcp的接收缓存区,如果有数据就进行处理,没有数据就调用yield调度其他生成器。
因为单单一个yield还无法很好地满足实际开发的需求,还需要做一些额外的开发,比如上面的tcp模型还需要把tcp设置成非阻塞等,所以就有了一些python的第三库,比如gevent,把许多需要用到的代码都进行了封装,使开发者使用起来更加方便快捷。下面列一个gevent的例子,实现并发获取多个URL内容。
#!/usr/bin/env python
'''
有关monkey patch可以浏览下面的网址看详细介绍
http://www.gevent.org/gevent.monkey.html#module-gevent.monkey
'''
from gevent import monkey; monkey.patch_all()
import gevent
import urllib2

def get_url(url):
resp = urllib2.urlopen(url)
data = resp.read()
print "get %s data %d"%(url, len(data))

gevent.joinall([
gevent.spawn(get_url, 'http://www.163.com/'),
gevent.spawn(get_url, 'http://www.126.com/'),
gevent.spawn(get_url, 'http://www.115.com/'),
])
默认情况下,python底层函数库比如socket遇到io等待是会阻塞当前的线程,monkey patch则是把python底层的io相关阻塞函数替换成非阻塞可以切换调度的,然开发者不用修改现有io函数接口,只需要在代码开头运行语句 from gevent import monkey; monkey.patch_all() 即可。
下面运行截图是开启了monkey patch后运行3次的屏幕输出,可以看到返回url数据的先后顺序是变化的,根据实际io数据返回的速度。



下面运行截图是没有开启monkey patch后运行3次的屏幕输出,可以看到每次返回顺序都是代码里面的请求顺序,不会出现io阻塞的时候自动调度。



2.后续
限于作者水平有限,短短一篇文章,难以诠释python yield的精华所在,需要在平时开发中多实战多体会。虽然单线程并发可以使用event loop这些模型,但是yield给我们提供了另外一种编程的思路方法,使得我们有了另外一种体会,我们可以使用yield来实现更加方便的状态机,消费者和生产者等等。

由睿江云研发人员提供,想了解更多,请登陆www.eflycloud.com
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: