Python中的协程大概经历了如下三个阶段:
1. 最初的生成器变形yield/send
2. 引入@asyncio.coroutine和yield from
3. 在最近的Python3.5版本中引入async/await关键字
一、生成器变形yield/send

普通函数中如果出现了yield关键字,那么该函数就不再是普通函数,而是一个生成器。

def mygen(alist):
    while len(alist) > 0:
        c = randint(0, len(alist)-1)
        yield alist.pop(c)
a = ["aa","bb","cc"]
c=mygen(a)
print(c)

输出:<generator object mygen at 0x02E5BF00>

1
    2
    3
    4
    5
    6
    7
    8
    9

像上面代码中的c就是一个生成器。生成器就是一种迭代器,可以使用for进行迭代。生成器函数最大的特点是可以接受外部传入的一个变量,并根据变量内容计算结果后返回。
这一切都是靠生成器内部的send()函数实现的。

def gen():
    value=0
    while True:
        receive=yield value
        if receive=='e':
            break
        value = 'got: %s' % receive

g=gen()
print(g.send(None))    
print(g.send('hello'))
print(g.send(123456))
print(g.send('e'))

1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13

上面生成器函数中最关键也是最易理解错的,就是receive=yield value这句,如果对循环体的执行步骤理解错误,就会失之毫厘,差之千里。
其实receive=yield value包含了3个步骤:
1、向函数外抛出(返回)value
2、暂停(pause),等待next()或send()恢复
3、赋值receive=MockGetValue() 。 这个MockGetValue()是假想函数,用来接收send()发送进来的值

执行流程:
1、通过g.send(None)或者next(g)启动生成器函数,并执行到第一个yield语句结束的位置。这里是关键,很多人就是在这里搞糊涂的。运行receive=yield value语句时,我们按照开始说的拆开来看,实际程序只执行了1,2两步,程序返回了value值,并暂停(pause),并没有执行第3步给receive赋值。因此yield value会输出初始值0。这里要特别注意:在启动生成器函数时只能send(None),如果试图输入其它的值都会得到错误提示信息。

2、通过g.send('hello'),会传入hello,从上次暂停的位置继续执行,那么就是运行第3步,赋值给receive。然后计算出value的值,并回到while头部,遇到yield value,程序再次执行了1,2两步,程序返回了value值,并暂停(pause)。此时yield value会输出”got: hello”,并等待send()激活。

3、通过g.send(123456),会重复第2步,最后输出结果为”got: 123456″。

4、当我们g.send(‘e’)时,程序会执行break然后推出循环,最后整个函数执行完毕,所以会得到StopIteration异常。

从上面可以看出, 在第一次send(None)启动生成器(执行1–>2,通常第一次返回的值没有什么用)之后,对于外部的每一次send(),生成器的实际在循环中的运行顺序是3–>1–>2,也就是先获取值,然后dosomething,然后返回一个值,再暂停等待。
二、yield from

看一段代码:

def g1():     
     yield  range(5)
def g2():
     yield  from range(5)

it1 = g1()
it2 = g2()
for x in it1:
    print(x)

for x in it2:
    print(x)

1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

输出:
range(0, 5)
0
1
2
3
4

这说明yield就是将range这个可迭代对象直接返回了。
而yield from解析了range对象,将其中每一个item返回了。
yield from iterable本质上等于for item in iterable: yield item的缩写版
来看一下例子,假设我们已经编写好一个斐波那契数列函数

def fab(max):
     n,a,b = 0,0,1
     while n < max:
          yield b
          # print b
          a, b = b, a + b
          n = n + 1
f=fab(5)

1
    2
    3
    4
    5
    6
    7
    8

fab不是一个普通函数,而是一个生成器。因此fab(5)并没有执行函数,而是返回一个生成器对象(生成器一定是迭代器iterator,迭代器一定是可迭代对象iterable)
现在我们来看一下,假设要在fab()的基础上实现一个函数,调用起始都要记录日志

def f_wrapper(fun_iterable):
    print('start')
    for item  in fun_iterable:
        yield item
     print('end')
wrap = f_wrapper(fab(5))
for i in wrap:
    print(i,end=' ')

1
    2
    3
    4
    5
    6
    7
    8

现在使用yield from代替for循环

import logging
def f_wrapper2(fun_iterable):
    print('start')
    yield from fun_iterable  #注意此处必须是一个可生成对象
    print('end')
wrap = f_wrapper2(fab(5))
for i in wrap:
    print(i,end=' ')

1
    2
    3
    4
    5
    6
    7
    8

再强调一遍:yield from后面必须跟iterable对象(可以是生成器,迭代器)
三、asyncio.coroutine和yield from

yield from在asyncio模块中得以发扬光大。之前都是我们手工切换协程,现在当声明函数为协程后,我们通过事件循环来调度协程。

先看示例代码:

import asyncio,random
@asyncio.coroutine
def smart_fib(n):
    index = 0
    a = 0
    b = 1
    while index < n:
        sleep_secs = random.uniform(0, 0.2)
        yield from asyncio.sleep(sleep_secs) #通常yield from后都是接的耗时操作
        print('Smart one think {} secs to get {}'.format(sleep_secs, b))
        a, b = b, a + b
        index += 1

@asyncio.coroutine
def stupid_fib(n):
    index = 0
    a = 0
    b = 1
    while index < n:
        sleep_secs = random.uniform(0, 0.4)
        yield from asyncio.sleep(sleep_secs) #通常yield from后都是接的耗时操作
        print('Stupid one think {} secs to get {}'.format(sleep_secs, b))
        a, b = b, a + b
        index += 1

if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    tasks = [
        smart_fib(10),
        stupid_fib(10),
    ]
    loop.run_until_complete(asyncio.wait(tasks))
    print('All fib finished.')
    loop.close()

1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34

yield from语法可以让我们方便地调用另一个generator。
本例中yield from后面接的asyncio.sleep()是一个coroutine(里面也用了yield from),所以线程不会等待asyncio.sleep(),而是直接中断并执行下一个消息循环。当asyncio.sleep()返回时,线程就可以从yield from拿到返回值(此处是None),然后接着执行下一行语句。
asyncio是一个基于事件循环的实现异步I/O的模块。通过yield from,我们可以将协程asyncio.sleep的控制权交给事件循环,然后挂起当前协程;之后,由事件循环决定何时唤醒asyncio.sleep,接着向后执行代码。
协程之间的调度都是由事件循环决定。
yield from asyncio.sleep(sleep_secs) 这里不能用time.sleep(1)因为time.sleep()返回的是None,它不是iterable,还记得前面说的yield from后面必须跟iterable对象(可以是生成器,迭代器)。
所以会报错:

yield from time.sleep(sleep_secs)
    TypeError: ‘NoneType’ object is not iterable

四、async和await

弄清楚了asyncio.coroutine和yield from之后,在Python3.5中引入的async和await就不难理解了:可以将他们理解成asyncio.coroutine/yield from的完美替身。当然,从Python设计的角度来说,async/await让协程表面上独立于生成器而存在,将细节都隐藏于asyncio模块之下,语法更清晰明了。
加入新的关键字 async ,可以将任何一个普通函数变成协程

import time,asyncio,random
async def mygen(alist):
    while len(alist) > 0:
        c = randint(0, len(alist)-1)
        print(alist.pop(c))
a = ["aa","bb","cc"]
c=mygen(a)
print(c)
输出:
<coroutine object mygen at 0x02C6BED0>

1
    2
    3
    4
    5
    6
    7
    8
    9
    10

在上面程序中,我们在前面加上async,该函数就变成一个协程了。

但是async对生成器是无效的。async无法将一个生成器转换成协程。
还是刚才那段代码,我们把print改成yield

async def mygen(alist):
    while len(alist) > 0:
        c = randint(0, len(alist)-1)
        yield alist.pop(c)
a = ["ss","dd","gg"]
c=mygen(a)
print(c)

1
    2
    3
    4
    5
    6
    7
    8

可以看到输出

<async_generator object mygen at 0x02AA7170>

1

并不是coroutine 协程对象

所以我们的协程代码应该是这样的

import time,asyncio,random
async def mygen(alist):
    while len(alist) > 0:
        c = random.randint(0, len(alist)-1)
        print(alist.pop(c))
        await asyncio.sleep(1)
strlist = ["ss","dd","gg"]
intlist=[1,2,5,6]
c1=mygen(strlist)
c2=mygen(intlist)
print(c1)

1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

要运行协程,要用事件循环
在上面的代码下面加上:

if __name__ == '__main__':
        loop = asyncio.get_event_loop()
        tasks = [
        c1,
        c2
        ]
        loop.run_until_complete(asyncio.wait(tasks))
        print('All fib finished.')
        loop.close()

1
    2
    3
    4
    5
    6
    7
    8
    9

就可以看到交替执行的效果。

理解Python协程:从yield/send到yield from再到async/await的更多相关文章

  1. 深入理解python协程

    目录 概述 生成器变形 yield/send yield send yield from asyncio.coroutine和yield from async和await 概述 由于 cpu和 磁盘读 ...

  2. Python协程笔记 - yield

    生成器(yield)作为协程 yield实际上是生成器,在python 2.5中,为生成器增加了.send(value)方法.这样调用者可以使用send方法对生成器发送数据,发送的数据在生成器中会赋值 ...

  3. 用yield实现python协程

    刚刚介绍了pythonyield关键字,趁热打铁,现在来了解一下yield实现协程. 引用官方的说法: 与线程相比,协程更轻量.一个python线程大概占用8M内存,而一个协程只占用1KB不到内存.协 ...

  4. python协程--yield和yield from

    字典为动词“to yield”给出了两个释义:产出和让步.对于 Python 生成器中的 yield 来说,这两个含义都成立.yield item 这行代码会产出一个值,提供给 next(...) 的 ...

  5. 00.用 yield 实现 Python 协程

    来源:Python与数据分析 链接: https://mp.weixin.qq.com/s/GrU6C-x4K0WBNPYNJBCrMw 什么是协程 引用官方的说法: 协程是一种用户态的轻量级线程,协 ...

  6. 终结python协程----从yield到actor模型的实现

    把应用程序的代码分为多个代码块,正常情况代码自上而下顺序执行.如果代码块A运行过程中,能够切换执行代码块B,又能够从代码块B再切换回去继续执行代码块A,这就实现了协程 我们知道线程的调度(线程上下文切 ...

  7. 从yield 到yield from再到python协程

    yield 关键字 def fib(): a, b = 0, 1 while 1: yield b a, b = b, a+b yield 是在:PEP 255 -- Simple Generator ...

  8. 从yield到yield from再到python协程

    yield 关键字 def fib(): a,b = 0,1 while 1: yield b a,b = b,a+b yield是在:PEP 255 -- Simple Generators 这个p ...

  9. day-5 python协程与I/O编程深入浅出

    基于python编程语言环境,重新学习了一遍操作系统IO编程基本知识,同时也学习了什么是协程,通过实际编程,了解进程+协程的优势. 一.python协程编程实现 1.  什么是协程(以下内容来自维基百 ...

随机推荐

  1. 后缀自动机(SAM)学习笔记

    目录 定义 SAM 的状态集 一些性质 SAM 的后缀链接 SAM 的转移函数 一些性质 算法构造 构造方法 时间复杂度证明 状态的数量 转移的数量 代码实现 实际应用 统计本质不同的子串个数 计算任 ...

  2. java 11 ZGC(可伸缩,低延迟的gc)

    ZGC, A Scalable Low-Latency Garbage Collector(Experimental) 可伸缩,低延迟的gc ZGC, 这应该是JDK11最为瞩目的特性, 没有之一. ...

  3. RQY大佬一晚报告总结

    首先基础真的很重要.今天所有学的东西,都是为明天做铺垫,(这样看好像每天都是基础)基础牢了,学东西也就一看就会的感觉吧. 其次,自学能力很重要,投入很重要.大佬说他自己也不是看一遍书就懂的,而是反复看 ...

  4. 再也不用c++的string了

    今天打比赛,觉得写法没错,但死活过不了大样例.调试时发现用cin读入一行臭长臭长的string类型变量时出锅了,原来几千个字符就读进来500个,导致一直WAWAWAWAWA. 最后yyj神犇帮我就删掉 ...

  5. css实现弹出框

    弹出框也是前端里面经常使用的一个应用场景了,最开始想到的是用js实现这个效果,看到codepen上面有用css实现的.其实就是用到了css里面的一个:target选择器+visibility属性. U ...

  6. GWAS群体分层 (Population stratification):利用plink对基因型进行PCA

    一.为什么要做祖先成分的PCA? GWAS研究时经常碰到群体分层的现象,即该群体的祖先来源多样性,我们知道的,不同群体SNP频率不一样,导致后面做关联分析的时候可能出现假阳性位点(不一定是显著信号位点 ...

  7. 金融量化分析【day111】:Matplotib-画布与子图

    一.画布与子图 1.实例 %matplotlib auto fig = plt.figure() ax = fig.add_subplot(2,2,1) ax2 = fig.add_subplot(2 ...

  8. 前端开发常用 Linux 命令

    clear // 清理命令窗口(可简写为cls) exit // 关闭命令窗口 mkdir 文件夹名 // 新建文件夹 cd 文件夹名 // 进入文件夹 cd .. // 返回上一级文件夹目录 rm ...

  9. 第一节:.Net版基于WebSocket的聊天室样例

    一. 说在前面的话 该篇文章为实时通讯系列的第一节,基于WebSocket编写了一个简易版聊天样例,主要作用是为引出后面SignalR系列的用法及其强大方便之处,通过这个样例与后续的SignalR对比 ...

  10. DQL用户、权限管理(mysql8.0)

    DQL用户.权限管理(mysql8.0) 查看所有的用户: use mysql; -- 使用mysql数据库 select * from user; -- 查询user表中的全部信息,也就是用户 创建 ...