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. lesson03

    3.1. 画 点 3.2. 基准平面 (重要) 1. 关于 点 的使用() 1.画一条直线,在线上画一个点(利用该点占该线段的百分比画出),通过该点画一条直线 2. 画一个长方体,定位到上表面.选择( ...

  2. 初识 go 语言

    目录 go简介 安装 hello world 函数 变量 常量 可见性规则 结束 前言: 最近组内要试水区块链,初步方案定为使用fabirc来弄,而fabric的智能合约就是用go写的,借此机会正好学 ...

  3. PKUWC2019爆0记

    PKUWC2019爆0记 访问量该骗的还是要骗. 1.20 坐了一天的高铁到jz了,热的一批 1.21 上午开营仪式 下午day1 打开发现有个地主斗 然后开T1 出题人你™搞笑吧放一道sb都能切的题 ...

  4. busybox编译

    sync.c:(.text.sync_main+0x7c): undefined reference to `syncfs'Coreutils—>sync选项去掉 nsenter.c:(.tex ...

  5. python集合(set)的运算

    1.交集 In [1]: a = {1,2,3,4} In [2]: b = {3,4,5,6} In [3]: a & b Out[3]: {3, 4} In [4]: a.intersec ...

  6. java 使用for循环打印杨辉三角形

    首先需要说明的问题: 什么是杨辉三角形? 如图所示:杨辉三角形由数字排列,基本的特点是两侧的数值均为1,其它位置的数值是基正上方的数值与其左上方的数值之和. 代码实现: package test; i ...

  7. SPFA+SLF+LLL

    关于SLF优化 朴素SPFA使用常规队列(FIFO)更新距离,并没有考虑优化出队顺序(dis值小的优先出队)可以在一开始就把各个点的dis值限值小,从而避免大量的松弛操作,从而提高效率.这就是SLF( ...

  8. HTML(二)HTML元素(整体结构,块级元素,内联元素,结构元素,交互元素,元素嵌套规则)

    HTML整体结构解释 <!DOCTYPE html> // 文件应以"<!DOCTYPE ......>"首行顶格开始,推荐使用"<!DOC ...

  9. [物理学与PDEs]第3章习题1 只有一个非零分量的磁场

    设磁场 ${\bf H}$ 只有一个非零分量, 试证明 $$\bex ({\bf H}\cdot\n){\bf H}={\bf 0}. \eex$$ 证明: 不妨设 ${\bf H}=(0,0,H_3 ...

  10. Karma (Test runner)

    Karma https://baike.baidu.com/item/%E7%BE%AF%E7%A3%A8/7618552?fromtitle=Karma&fromid=878453 1,意译 ...