码迷,mamicode.com
首页 > 编程语言 > 详细

python协程

时间:2019-02-05 23:47:50      阅读:195      评论:0      收藏:0      [点我收藏+]

标签:def   group   例子   ret   直接   ror   type   create   比较   

协程

定义:协程不是进程或线程,其执行过程更类似于子例程,或者说不带返回值的函数调用。
协程与线程:一个程序可以包含多个协程,可以对比与一个进程包含多个线程,因而下面我们来比较协程和线程。我们知道多个线程相对独立,有自己的上下文,切换受系统控制;而协程也相对独立,有自己的上下文,但是其切换由自己控制,由当前协程切换到其他协程由当前协程来控制。
协程与生成器:从句法上看,协程类似于生成器,都是定义体中包含yield关键字的函数。但协程中yield通常出现在表达式右边,可以产出值也可以不产出值--如果yield关键字后面没有表达式,那么生成器产出None。
把yield视作控制流程的方式,就容易理解协程。

协程的状态

一个简单协程示例:

def simple_coroutine():
    print(start)
    x = yield         #yield右边为空,默认产出None
    print(received:, x)

my_coro = simple_coroutine()    
print(my_coro)
next(my_coro)         #启动生成器
my_coro.send(40)    #调用后,yield表达式会计算出40,然后执行到下一个yield或终止

#结果 <generator object simple_coroutine at 0x000001C17BB43A98> #生成器对象 start received: 40 StopIteration #与生成器行为一样,抛出StopIteration异常

类似于线程、进程,协程可以身处四个状态中的某一个:

GEN_CREATED               //就绪,等待开始执行

GEN_RUNN                     //执行,解释器正在执行

GEN_SUSPENDED         //暂停,在yield表达式处暂停

GEN_CLOSED                 //结束,执行结束

协程状态在python中可以使用inspect.getgeneratorstate()函数获取:

def simple_coroutine_2(a):
    print(start:a = , a)
    b = yield a      
    print(received:b = , b)
    c = yield a + b
    print(received:c = , c)


my_coro = simple_coroutine_2(14)
from inspect import getgeneratorstate
print(getgeneratorstate(my_coro))
next(my_coro)    #执行到第一个yield,产出a的值,等待为b赋值
print(getgeneratorstate(my_coro))
my_coro.send(28)        #见结果,b值为28,调用方将28发送给协程
print(getgeneratorstate(my_coro))
try:
    my_coro.send(99)
except StopIteration:
    pass
print(getgeneratorstate(my_coro))  


#结果
GEN_CREATED      #协程未启动
start:a =  14        
GEN_SUSPENDED     #协程暂停
received:b =  28
GEN_SUSPENDED   #协程暂停
received:c =  99
GEN_CLOSED     #协程结束

预激协程

在上一个示例中,未调用next()函数时,协程处于GEN_CREATED状态。处于GEN_CREATED状态的协程没有激活,须使用next()函数激活协程才能将值发送给协程,否则报错。

my_coro = simple_coroutine_2(14)
my_coro.send(15)

#结果
TypeError: cant send non-None value to a just-started generator

调用next()函数后,协程会向前执行到yield表达式,产出值,并暂停等待调用方发送值。

调用next()函数预激协程

或使用装饰器

from functools import wraps

#定义一个预激协程的装饰器
def coroutine(func):
    @wraps(func)
    def primer(*args, **kwargs):
        gen = func(*args, **kwargs)   #调用被装饰函数,获取生成器对象
        next(gen)    #预激协程
        return gen   #返回生成器
    return primer

#使用装饰器,计算历史平均值
@coroutine
def averager():
    total = 0.0
    count = 0
    average = None
    while True:
        term = yield average
        total += term
        count += 1
        average = total/count


coro_avg = averager()
from inspect import getgeneratorstate
print(getgeneratorstate(coro_avg))
print(coro_avg.send(10))
print(coro_avg.send(20))

#结果
GEN_SUSPENDED   #一开始就处于暂停状态
10.0
15.0

控制协程

generator.send(value)               //生成器调用方可以使用send发送数据,这个成为yield表达式的值,并前进到下一个yield处

 

generator.throw(exc_type[, exc_value[, traceback]])            //致使生成器在暂停的yield表达式处抛出指定的异常。如果生成器处理了抛出的异常,代码会向前执行到下一个yield表达式,而产出的值会调用generator.throw方法得到返回值。如果生成器没有处理抛出的异常,异常会向上冒泡。

 

generator.close()    //致使生成器在暂停的yield表达式处抛出GeneratorExit异常。如果生成器没有处理这个异常,或者抛出StopIteration异常,调用方不会报错。如果收到GeneratorExit异常,生成器一定不能产出值,否则解释器会抛出RuntimeError。生成器抛出的其他异常会向上冒泡。

 

异常处理测试代码:

class DemoException(Exception):
    """此次演示的定义异常类型"""

def demo_exc_handling():
    print(start)
    while True:
        try:
            x = yield
        except DemoException:   #特别处理
            print(*** DemoException handled.Continuing...)
        else:
            print(received:{!r}.format(x))
    raise RuntimeError(this line should never run.)   
    #这一行代码永远不会执行,未处理的异常才会终止循环,而一旦出现未处理的异常,协程会立刻终止

激活和关闭demo_exc_handling,没有异常:

exc_coro = demo_exc_handling()
next(exc_coro)
exc_coro.send(10)
exc_coro.send(20)
exc_coro.close()
from inspect import getgeneratorstate
print(getgeneratorstate(exc_coro))

#结果
start
received:10
received:20
GEN_CLOSED

把DemoException异常传入demo_exc_handling协程,它会处理然后继续运行:

exc_coro = demo_exc_handling()
next(exc_coro)
exc_coro.send(10)
exc_coro.throw(DemoException)
from inspect import getgeneratorstate
print(getgeneratorstate(exc_coro))


#结果
start
received:10
*** DemoException handled.Continuing...
GEN_SUSPENDED

尝试传入没有处理的异常:

exc_coro = demo_exc_handling()
next(exc_coro)
exc_coro.send(10)
exc_coro.throw(ZeroDivisionError)
from inspect import getgeneratorstate
print(getgeneratorstate(exc_coro))

#结果
start
received:10
#报错
GEN_CLOSED

若不管协程如何结束都想做一些清理工作,要把协程定义体中的相关代码放入try/finally块中:

class DemoException(Exception):
    """此次演示的定义异常类型"""

def demo_exc_handling():
    print(start)
    try:
        while True:
            try:
                x = yield
            except DemoException:
                print(*** DemoException handled.Continuing...)
            else:
                print(received:{!r}.format(x))
    finally:
        print(ending)

获取协程返回值

使用return直接返回值是可行的,但和普通函数不同,return语句的返回值会赋值给StopIteration异常的一个属性

from collections import namedtuple
Result = namedtuple(Result, count average)

def averager():
    total = 0.0
    count = 0
    average = None
    while True:
        term = yield
        if term is None:
            break
        total += term
        count += 1
        average = total/count
    return Result(count, average)

coro_avg = averager()
next(coro_avg)
coro_avg.send(10)
coro_avg.send(20)
coro_avg.send(None)


#结果
StopIteration: Result(count=2, average=15.0)

捕获StopIteration异常,获取返回值:

coro_avg = averager()
next(coro_avg)
coro_avg.send(10)
coro_avg.send(20)
try:
    coro_avg.send(None)
except StopIteration as e:
    print(e.value)

#结果
Result(count=2, average=15.0)

yield from 

它的一个用法是简化for循环中的yield表达式:

def gen():
    for c in ABC:
        yield c

    for i in range(1, 5):
        yield i

def gen_2():
    yield from ABC
    yield from range(1, 5)

两个函数是一样的功能。

 

在协程中yield from会创建通道,把内层生成器直接与外层生成器的客户端联系起来。二者可以直接发送和产出值,还可以直接传入异常

一个至关重要的一点:在生成器gen中使用yield from subgen()时,subgen()会获得控制权,把产出的值传给gen的调用方,即调用方可以直接控制subgen。与此同时,gen会阻塞,等待subgen终止。

三个术语

委派生成器:包含yield from <iterable>表达式的生成器函数

子生成器:从yield from表达式中<iterable>部分获取的生成器。

调用方:调用生成器的客户端代码。

下面一个例子是:从data字典中读取虚构的七年级男女学生的体重和身高,获取生成平均结果。

from collections import namedtuple
Result = namedtuple(Result, count average)

#子生成器
def averager():
    total = 0.0
    count = 0
    average = None
    while True:
        term = yield
        if term is None:  #!!!!至关重要的终止条件
            break
        total += term
        count += 1
        average = total/count
    return Result(count, average)

#委派生成器
def grouper(result, key):
    while True:    #每次迭代创建一个新的averager实例;每个实例都是作为协程使用的生成器
        result[key] = yield from averager()  #grouper发送的每个值都会经由yield from处理,通过管道传递给averager实例。grouper会在yield from
        #表达式暂停,等待averager处理客户端发来的值。

#调用方,即驱动函数
def main(data):
    results = {}
    for key, values in data.items():
        group = grouper(results, key)   #group是调用grouper函数得到的生成器对象,group作为协程使用
        next(group)   #预激协程
        for value in values:  #把各个value传给grouper,传入的值最终到达averager函数中term = yield这一个,grouper函数不知情
            group.send(value)
        group.send(None)    #终止当前averager实例

    report(results)

#输出函数
def report(results):
    for key, result in sorted(results.items()):
        group, unit = key.split(;)
        print({:2} {:5} averaging {:.2f}{}.format(result.count, group, result.average, unit))


data = {
    girls;kg: [40.9, 38.5, 44.3, 42.2, 45.2, 41.7, 44.5, 38.0, 40.6, 44.5],
    girls;m: [1.6, 1.51, 1.4, 1.3, 1.14, 1.39, 1.33, 1.46, 1.45, 1.43],
    boys;kg: [39.0, 40.8, 43.2, 40.8, 43.1, 38.6, 41.4, 40.6, 36.3],
    boys;m: [1.38, 1.5, 1.32, 1.25, 1.37, 1.48, 1.25, 1.49, 1.46],
    }

if __name__ == __main__:
    main(data)

如果没有.send(None),那么averager子生成器不会终止,results[key]赋值语句不会执行。

例子表达最关键的一点是:如果子生成器不终止,委派生成器会在yield from表达式处永远暂停,这样程序不会向前执行,yield from把控制权转交给调用方了,但有任务没有执行。

 

委派生成器相当于管道,所以可以把任意数量的委派生成器连接在一起:一个委派生成器使用yield from调用一个子生成器,而那个子生成器本身也是委派生成器,使用yield from调用另一个生成器,以此类推。最终,这个链条要以一个yield表达式的简单生成器结束或以任何可迭代对象结束。任何yield from链条都必须由客户端驱动,在最外层委派生成器上调用next()函数或者.send()方法。

 

关于yield from 六点重要的说明

  1. 子生成器产出的值都直接传给委派生成器的调用方(即客户端代码)
  2. 使用send()方法发送给委派生成器的值都直接传给子生成器。如果发送的值为None,那么会给委派调用子生成器的__next__()方法。如果发送的值不是None,那么会调用子生成器的send方法,如果调用的方法抛出StopIteration异常,那么委派生成器恢复运行,任何其他异常都会向上冒泡,传给委派生成器
  3. 生成器退出时,生成器(或子生成器)中的return expr表达式会出发StopIteration(expr)异常抛出
  4. yield from表达式的值是子生成器终止时传给StopIteration异常的第一个参数。yield from 结构的另外两个特性与异常和终止有关。
  5. 传入委派生成器的异常,除了GeneratorExit之外都传给子生成器的throw()方法。如果调用throw()方法时抛出StopIteration异常,委派生成器恢复运行。StopIteration之外的异常会向上冒泡,传给委派生成器
  6. 如果把GeneratorExit异常传入委派生成器,或者在委派生成器上调用close()方法,那么在子生成器上调用clsoe()方法,如果它有的话。如果调用close()方法导致异常抛出,那么异常会向上冒泡,传给委派生成器,否则委派生成器抛出GeneratorExit异常

以上来自《流畅的python》

python协程

标签:def   group   例子   ret   直接   ror   type   create   比较   

原文地址:https://www.cnblogs.com/lht-record/p/10349487.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!