中国网管论坛

 找回密码
 注册

QQ登录

只需一步,快速开始

网管赚钱丨推荐设2345主页游戏攻略/CS/LOL/DNFLinux常用命令大全云计算/云技术频道
IP地址在线计算器网管软件,网管工具论坛积分购买 
查看: 378|回复: 0

[分享交流] 人生苦短我用python[0x02] yield浅析

[复制链接]
发表于 2017-5-27 15:16:53 | 显示全部楼层 |阅读模式
1.yield介绍

先来几个单词翻译,根据词霸的翻译有以下截图

python0x0501.jpg

python0x0502.jpg

python0x0503.jpg

一开始学习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个生成器的运行。

python0x0504.jpg

看完了代码和运行结果,我们再来讲解生成器,yield这些概念。test_list.append(test(i*100)) 我们看到,test(i*00)实际上是不会触发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 geventimport urllib2def 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数据返回的速度。

python0x0505.jpg

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

python0x0506.jpg

2.后续

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

由睿江云研发人员提供,想了解更多,请登陆www.eflycloud.com

您需要登录后才可以回帖 登录 | 注册

本版积分规则

2345

QQ|小黑屋|手机版|Archiver|网管之家 ( 沪ICP备08026629号 ) 

GMT+8, 2017-6-27 23:34

Powered by Discuz! X3.1

© 1999-2014 bitsCN.com

快速回复 返回顶部 返回列表