通常在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
整个过程如下:
  1. 首先调用c.next()启动生成器;
  2. 然后,一旦生产了东西,通过c.send(n)切换到consumer执行;在这里c.send(n),其中n值就传递给了consumer中的n,(n=yield r). 因此yield左边的值代表接受的值,右边为返回的值
  3. consumer通过yield拿到消息,处理,又通过yield把结果传回;返回的值是consumer中的r
  4. produce拿到consumer处理的结果,继续生产下一条消息;
  5. 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学习笔记:第十六章:协程的更多相关文章

  1. 流畅的python第十六章协程学习记录

    从句法上看,协程与生成器类似,都是定义体中包含 yield 关键字的函数.可是,在协程中,yield 通常出现在表达式的右边(例如,datum = yield),可以产出值,也可以不产出——如果 yi ...

  2. 流畅python学习笔记第十八章:使用asyncio编写服务器

    在这一章中,将使用asyncio写一个TCP服务器.这个服务器的作用是通过规范名称查找Unicode字符,来看下代码: import asyncio from charfinder import Un ...

  3. 流畅python学习笔记第十八章:使用asyncio包处理并发(一)

    首先是线程与协程的对比.在文中作者通过一个实例分别采用线程实现和asynchio包实现来比较两者的差别.在多线程的样例中,会用到join的方法,下面来介绍下join方法的使用. 知识点一:当一个进程启 ...

  4. 流畅python学习笔记:第十一章:抽象基类

    __getitem__实现可迭代对象.要将一个对象变成一个可迭代的对象,通常都要实现__iter__.但是如果没有__iter__的话,实现了__getitem__也可以实现迭代.我们还是用第一章扑克 ...

  5. 流畅python学习笔记:第十七章:并发处理

    第十七章:并发处理 本章主要讨论Python3引入的concurrent.futures模块.在python2.7中需要用pip install futures来安装.concurrent.futur ...

  6. 流畅python学习笔记:第十七章:并发处理二

    本章讨论python3.2引入的concurrent.futures模块.future是中文名叫期物.期物是一种对象,表示异步执行的操作 在很多任务中,特别是处理网络I/O.需要使用并发,因为网络有很 ...

  7. python学习笔记-(十六)python操作mysql

    一. mysql安装 1. windows下安装mysql 1.1. 下载源: http://dev.mysql.com/downloads/installer/,请认准对应版本 Windows (x ...

  8. Python学习笔记第二十六周(Django补充)

    一.基于jQuery的ajax实现(最底层方法:$.jax()) $.ajax( url: type:''POST“ ) $.get(url,[data],[callback],[type])  #c ...

  9. Python学习笔记第十六周

    目录: 一.CSS补充 1.页面布局 二.JavaScript补充 1.条件判断 2.函数分类 3.序列化 4.转义 5.eval 6.时间 7.作用域 三.DOM 1.间接查找 文本操作 样式操作 ...

  10. python学习笔记(十六)之文件

    打开文件用open函数 open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=Tru ...

随机推荐

  1. Jenkins插件HTML Publisher Plugin的使用

    前提: 下载插件HTML Publisher plugin 一.安装 安装好HTML Publisher plugin之后,会在新建或者编辑项目时,在[增加构建后操作步骤]出现[Publish HTM ...

  2. IOS7开发~API变化

    1.弃用 MKOverlayView 及其子类,使用类 MKOverlayRenderer: 2.弃用 Audio Toolbox framework 中的 AudioSession API,使用AV ...

  3. SQLITE3 --详解

    由于我主要负责我们小组项目数据库模块的部分所以这几天都一直在研究在iphone中最为常用的一个简单数据库sqlite,自己也搜集很多资料,因此在 这里总结一下这几天的学习成果: Sqlite 操作简明 ...

  4. angular使用canvas操作时报错

    最近,用 angular 前端框架为应用登录新增图形验证码认证,由于没有现成的插件,于是便使用canvas+js操作,也是可以正常使用,但是在编译阶段却有个报错: ERROR in src/app/l ...

  5. 【ActiveMQ】1.下载安装启动使用

    官网下载:http://activemq.apache.org/activemq-5121-release.html 官网指导文档:http://activemq.apache.org/version ...

  6. Facebook Rebound 弹性动画库 源码分析

    Rebound源码分析 让动画不再僵硬:Facebook Rebound Android动画库介绍一文中介绍了rebound这个库. 对于想体验一下rebound的效果,又懒得clone和编译代码的, ...

  7. 卸载django

    首先找到django安装路径 Python import sys; sys.path = sys.path[1:]; import django; print(django.__path__) 得到 ...

  8. Object-C 类

    Classes 类 像其它的面向对象的语言一样,Object-C也提供创建对象的蓝本.即类. 首先我们在类中定义一些能够反复使用的属性和方法. 然后,我们实例化类,即对象,之后就能够使用属性和訪问. ...

  9. windows下用vscode写C++

    [本文参考:https://www.cnblogs.com/zhuzhenwei918/p/9057289.html  和 https://www.zhihu.com/question/3031589 ...

  10. AngularJS中,<span class="bluetext" ng-bind="ctrl.user.name|uppercase"></span>和{{ctrl.user.name|uppercase}}是等价的,但不等于<span class="bluetext" ng-bind="ctrl.user.name|uppercase"/>

    代码下载:https://files.cnblogs.com/files/xiandedanteng/angularjsAttenSpan.rar AngularJS中,<span class= ...