• python协程


    协程

    定义:协程不是进程或线程,其执行过程更类似于子例程,或者说不带返回值的函数调用。
    协程与线程:一个程序可以包含多个协程,可以对比与一个进程包含多个线程,因而下面我们来比较协程和线程。我们知道多个线程相对独立,有自己的上下文,切换受系统控制;而协程也相对独立,有自己的上下文,但是其切换由自己控制,由当前协程切换到其他协程由当前协程来控制。
    协程与生成器:从句法上看,协程类似于生成器,都是定义体中包含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: can't 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》

  • 相关阅读:
    周末小练习
    第十二届全国大学生信息安全竞赛总结与反思
    sql注入学习心得与sqlmap使用心得
    2019“嘉韦思”杯RSA256题目wp
    斐波那契数列求解的三种方法
    二叉树的下一个节点
    替换空格
    二维数组中的查找
    不修改数组找出重复数字
    数组中重复数字
  • 原文地址:https://www.cnblogs.com/lht-record/p/10349487.html
Copyright © 2020-2023  润新知