云服务器

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

2017-06-22 14:48:02 0

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()

运行程序第二次的时候,按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来实现更加方便的状态机,消费者和生产者等等。

 

上一篇: 无

微信关注

获取更多技术咨询