流畅python学习笔记:第十六章:协程
通常在python进行编程一般都是使用多线程或者多进程来实现。这里介绍另外一种并发的方式,就是协程,但和多线程以及多进程不一样的是,协程是运行在单线程当中的并发。来看下具体的例子:
- def simple_coroutine():
- print 'corouting started'
- x=yield (1)
- print 'coroutine received %d' % x
- if __name__=="__main__":
- my_core=simple_coroutine()
- my_core.next() (2) 也可以写成next(my_core)
- my_core.send(12) (3)
- 执行结果如下:
E:\python2.7.11\python.exe E:/py_prj/fluent_python/chapter16.py
Traceback (most recent call last):
File "E:/py_prj/fluent_python/chapter16.py", line 33, in <module>
my_core.send(12)
StopIteration
corouting started
coroutine received 12
来看下代码的执行过程:
(1)x=yield为协程的表达式.x等于后面send发送进来的值。x是等于第三步中发送的值,也就是12.
(2)首先调用next函数。调用后生成器才能启动
(3)发送一个值,send方法会传入一个参数,该参数就是yield表达式的值,可以在生成器函数里面被接收。该参数协程定义体中的yield表达式会计算出12.现在协程处于工作状态,一直运行到下一个yield表达式,或者终止。
从这个执行过程来看,协程不是多线程的运行,而是单线程的运行。且运行方式和中断类。而且协程也有自身的状态总共有4个:1 GEN_CREATED 等待开始执行。2 GEN_RUNNING 解释器正在执行 3 GEN_SUSPEND 在yield表达式出暂停 4 GEN_CLOSED 执行结束。我们用getgeneratorstate来观测下状态的变化。通过getgeneratorstate(my_core)就可以得出具体的状态。但这个只限于python3才有,python2.7是没有的。
我们在来看另外一个协程的例子:计算移动平均值:
- def averager():
- total=0.0
- count=0
- average=None
- while True:
- term=yield average (1)
- total+=term
- count+=1
- average=total/count
- if __name__=="__main__":
- core_average=averager()
- print core_average.next() (a)
- print core_average.send(10)
- print core_average.send(30)
- print core_average.send(5)
- 这是一个无限循环,也就是说只要调用方不停的发送值给这个协程。它就会一直收值。然后生成结果。我们来看下它的运行过程。
- (1) 执行core_average.next的时候。执行到averager中的(1)位置,此时产生一个average,此时还没开始计算,因此产生的average值为None。此时协程在yield处暂停。等到调用发送值
- (2) core_average.send(10)激活协程,并且将10赋值给term。并更新total,count,average的值。然后开始while循环的下一次迭代。在yield处停止,产出average,此时average已经在上一次循环中被更新为10/1=10。然后继续等待激活协程
- (3) core_average.send(30) 激活协程,并且将20赋值给term。并更新total,count,average的值。然后开始while循环的下一次迭代。在yield处停止,产出average,此时average已经在上一次循环中被更新为(30+10)/2=20。然后继续等待激活协程
- (4) core_average.send(5) 激活协程,执行步骤和之前一样,此时average=(10+30+5)/3=15
- 最终的执行结果如下,符合我们的预期
- E:\python2.7.11\python.exe E:/py_prj/fluent_python/chapter16.py
- None
- 10.0
- 20.0
- 15.0
那么协程是如何停止的,前面的simple_coroutine例子在结束后会抛出StopIteration异常。那么在averager这个无线循环中如何退出呢。来看下下面的这个调用:
- core_average=averager()
- print core_average.next()
- print core_average.send(10)
- print core_average.send('abc')
- 将core_average.send(30)改成core_average.send('abc'),也就是发送一个字符,而不是发送一个整数。这时候的执行结果会产生一个异常而退出。证明了协程中未处理的异常会向上冒泡,传给next函数或send方法的调用方,也就是触发协程的对象。
- Traceback (most recent call last):
- File "E:/py_prj/fluent_python/chapter16.py", line 44, in <module>
- print core_average.send('abc')
- File "E:/py_prj/fluent_python/chapter16.py", line 36, in averager
- total+=term
- TypeError: unsupported operand type(s) for +=: 'float' and 'str'
基于这个实现,python2.5后可以调用两个方法,将异常发给协程。这个两个方法是throw和close
使用throw方法与协程通信:
一旦协程开启,仅仅通过send与yield只能对协程进行简单的控制。throw方法提供了在协程内引发异常的接口。通过主叫者调用throw在协程内引发异常,协程捕获异常的方式,可以实现主叫者与协程之间的通信。
需要注意的是,使用throw方法在协程内引发的异常,如果没有被捕获,或者内部又重新raise了不同的异常,那么这个异常会传播到主叫者。
同时throw方法同send与next一样,都会使协程继续运行,并返回下一个yield表达式中的表达式值。且同样的,如果不存在下一个yield表达式协程就结束了,主叫方会收到StopIteration Exception。
使用close方法来结束协程
close方法与throw方法类似,都是在协程暂停的位置引发一个异常,从而向协程发出控制信息。不同于throw方法可以抛出自定义异常,close方法会固定抛出GeneratorExit异常。当这个异常没有被捕获或者引发StopIteration Exception时,close方法会正常返回。这个比较好理解,协程设计者捕获了GeneratorExit异常并完成清理,保证清理干净了,所以最后不会再有yield返回值引发StopItertation。如果因为设计错误导致仍然有下一个yield,那么就会抛出RuntimeError.
来看下close的使用:
- core_average=averager()
- print core_average.next()
- print core_average.send(10)
- print core_average.send(30)
- core_average.close()
- print core_average.send(5)
- 在执行core_average.send(5)的时候抛出了StopIteration。因为在前一步执行了core_average.close()方法停止了协程
Traceback (most recent call last):
File "E:/py_prj/fluent_python/chapter16.py", line 46, in <module>
print core_average.send(5)
StopIteration
None
10.0
20.0
- 我们来看一个更加具体的例子:一个关于生产者和消费者的例子,传统的生产者和消费者的是一个线程写消息,一个线程取消息,通过锁机制控制,但一不小心就会死锁,我们用协程的方式来改造下。
- def consumer():
- r=''
- while True:
- n=yield r
- if not n:
- return
- print 'Consumer consuming....%d' % n
- time.sleep(1)
- r='OK'
- def producer(c):
- c.next()
- n=0
- while n<5:
- n=n+1
- print 'Producer producing....%d' % n
- r=c.send(n)
- print 'Consumer return %s' % r
- c.close()
- if __name__=="__main__":
- c=consumer()
- producer(c)
- E:\python2.7.11\python.exe E:/py_prj/fluent_python/chapter16.py
- Producer producing....1
- Consumer consuming....1
- Consumer return OK
- Producer producing....2
- Consumer consuming....2
- Consumer return OK
- Producer producing....3
- Consumer consuming....3
- Consumer return OK
- Producer producing....4
- Consumer consuming....4
- Consumer return OK
- Producer producing....5
- Consumer consuming....5
- Consumer return OK
- 整个过程如下:
- 首先调用c.next()启动生成器;
- 然后,一旦生产了东西,通过c.send(n)切换到consumer执行;在这里c.send(n),其中n值就传递给了consumer中的n,(n=yield r). 因此yield左边的值代表接受的值,右边为返回的值
- consumer通过yield拿到消息,处理,又通过yield把结果传回;返回的值是consumer中的r
- produce拿到consumer处理的结果,继续生产下一条消息;
- produce决定不生产了,通过c.close()关闭consumer,整个过程结束。
yield from:
在python3中,引入了yield from的语法结构。在生成器gen中使用yield from subgen()时,subgen会获得控制权,把产出的值传给gen的调用方。也就是说调用方可以直接控制subgen,同时gen会被阻塞,等待subgen终止。来看下yield和yield from的使用区别
def gen():
for c in 'AB':
yield c
for i in range(1,3):
yield i
def gen1():
yield from 'AB'
yield from range(1,3)
在这里我们可以看到使用yiled from比yield更简洁。而yield from ‘AB’其实就是代替了for c in ‘AB’: yield c的作用。那么从这里总结出yield from的第一个特征:yield from后面是一个迭代器。也称为子生成器。在这里gen1被称为委派生成器。
调用关系如下图所示:
yield from的作用就是将最外层的调用方与最内层的子生成器连接起来。这样二者就可以直接发送和产出值。而中间的这个桥梁或者是管道就是委派生成器。我们先不看图片中的例子,我们来看个我们之前的例子。
在http://www.cnblogs.com/zhanghongfeng/p/7301675.html这篇帖子中,我们利用yield实现了一个生产,消费的模型。下面我们用yield from来改造这个程序
def consumer():
r=''
while True:
n=yield r
if not n:
return
print('Consumer consuming....%d' % n)
time.sleep(1)
r='OK'
def product():
ret=yield from consumer()
print('Consumer return %s' % ret)
if __name__=="__main__":
p=product()
n=0
next(p)
while n<5:
n+=1
print('Product producing....%d' % n)
p.send(n)
通过yield from我们可以将代码精简。在上面的代码中,product就是委派生成器,consumer就是子生成器。p.send(n)将生产的数据发给消费者。从而使得消费者进行消费。
下面来看一个通过生成器模仿的出租车运营的代码:
Event=collections.namedtuple('Event','time proc action')
def taxi_process(ident,trips,start_time=0):
time=yield Event(start_time,ident,'leave garage')
for i in range(trips):
time=yield Event(time,ident,'pick up passenger')
time=yield Event(time,ident,'drop off passenger')
yield Event(time,ident,'going home')
if __name__=="__main__":
taxi=taxi_process(ident=13,trips=2,start_time=0)
next(taxi)
ret=taxi.send(7)
print(ret)
ret =taxi.send(23)
print(ret)
ret =taxi.send(5)
print(ret)
ret =taxi.send(48)
print(ret)
ret =taxi.send(1)
print(ret)
(1)首先创建一个生成器对象,表示一辆出租车,这辆出租车的编号是13,从t=0的时候开始工作
(2) next(taxi)产生第一个事件。然后发送当前时间,这里在时间上加7,意思是这辆出租车7分钟后找到第一个乘客
(3)for循环产生了的一个行程
(4)发送时间23,表示第一个乘客的形成持续了23分钟
(5)依次进行上面的流程,直到两次行程完成后,for循环结束,产出’going home’事件
(6)如果再尝试把值发给协程,会执行到协程的末尾,协程返回后,解释器抛出StopIteration.
流畅python学习笔记:第十六章:协程的更多相关文章
- 流畅的python第十六章协程学习记录
从句法上看,协程与生成器类似,都是定义体中包含 yield 关键字的函数.可是,在协程中,yield 通常出现在表达式的右边(例如,datum = yield),可以产出值,也可以不产出——如果 yi ...
- 流畅python学习笔记第十八章:使用asyncio编写服务器
在这一章中,将使用asyncio写一个TCP服务器.这个服务器的作用是通过规范名称查找Unicode字符,来看下代码: import asyncio from charfinder import Un ...
- 流畅python学习笔记第十八章:使用asyncio包处理并发(一)
首先是线程与协程的对比.在文中作者通过一个实例分别采用线程实现和asynchio包实现来比较两者的差别.在多线程的样例中,会用到join的方法,下面来介绍下join方法的使用. 知识点一:当一个进程启 ...
- 流畅python学习笔记:第十一章:抽象基类
__getitem__实现可迭代对象.要将一个对象变成一个可迭代的对象,通常都要实现__iter__.但是如果没有__iter__的话,实现了__getitem__也可以实现迭代.我们还是用第一章扑克 ...
- 流畅python学习笔记:第十七章:并发处理
第十七章:并发处理 本章主要讨论Python3引入的concurrent.futures模块.在python2.7中需要用pip install futures来安装.concurrent.futur ...
- 流畅python学习笔记:第十七章:并发处理二
本章讨论python3.2引入的concurrent.futures模块.future是中文名叫期物.期物是一种对象,表示异步执行的操作 在很多任务中,特别是处理网络I/O.需要使用并发,因为网络有很 ...
- python学习笔记-(十六)python操作mysql
一. mysql安装 1. windows下安装mysql 1.1. 下载源: http://dev.mysql.com/downloads/installer/,请认准对应版本 Windows (x ...
- Python学习笔记第二十六周(Django补充)
一.基于jQuery的ajax实现(最底层方法:$.jax()) $.ajax( url: type:''POST“ ) $.get(url,[data],[callback],[type]) #c ...
- Python学习笔记第十六周
目录: 一.CSS补充 1.页面布局 二.JavaScript补充 1.条件判断 2.函数分类 3.序列化 4.转义 5.eval 6.时间 7.作用域 三.DOM 1.间接查找 文本操作 样式操作 ...
- python学习笔记(十六)之文件
打开文件用open函数 open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=Tru ...
随机推荐
- 2016北京集训测试赛(七)Problem A: 自动机
Solution 注意到这一题并不要求字符串最短或者是字典序最小, 因此直接构造就可以了. 我们对于每个点\(u \ne 0\)找到一个串\(S\), 使得\(T(u, S) = T(0, S)\), ...
- c# datetime是一年中的第几周
public static int WeekOfYear(DateTime dt, CultureInfo ci) { return ci.Calendar.GetWeekOfYear(dt, ci. ...
- Ruby Time And DateTime之Time in Core
今天遇到一个问题,就是在Ruby中对于Time和DateTime的使用,不是很明了,现在研究一下: 先说Time: 在Ruby2.0中关于Time有两处定义一个是在Core中,http://www.r ...
- pt-query-digest 实践(转)
mysql slowlog 使用与介绍 slow_query_log =1-----是否打开 slow_query_log_file = /data/mysql_data/node-1/mysql-s ...
- iOS博客列表
国外 iOSDevWeekly NSHipster NSBlog objcio Raywenderlich Bignerdranch NSScreencast 需FQ Pilky.me jeremyw ...
- MongoDB下载安装測试及使用
1.下载安装 64位:mongodb-win32-x86_64-enterprise-windows-64-2.6.4-signed.msi 余数为1的 db.collection.find({ &q ...
- linux中find的用法
找所在目录的文件用 find -name “file*” -print #注意不要用加文件路径,查找文件也好用双双引号括住: 也可以 find ./ -name “file*” -print
- 版本号控制软件:TortoiseSVN高速上手
百度百科对于SVN的一点解释: TortoiseSVN是Subversion版本号控制系统的一个免费开源client,能够超越时间的管理文件和文件夹.文件保存在中央版本号库,除了能记住文件和文件夹的每 ...
- Android Studio中利用JavaDoc生成项目API文档
1. 在Android Studio中的菜单项中点击Generate JavaDoc
- FreeMark的list应用
语法:<#if></#if>后台传送List,前台html页面中获取该list并显示: <#if userList?exists> <#list userLi ...