python——协程

 协程

本质是在单个线程中管理并发活动。

        字典为动词“to yield”给出了两个释义: 产出和让步。 对于 Python 生成器中的 yield 来说, 这两个含义都成立。 yield item 这行代码会产出一个值, 提供给 next(...) 的调用方; 此外, 还会作出让步, 暂停执行生成器, 让调用方继续工作, 直到需要使用另一个值时再调用next()。 调用方会从生成器中拉取值。
        从句法上看, 协程与生成器类似, 都是定义体中包含 yield 关键字的函数。 可是, 在协程中, yield 通常出现在表达式的右边(例如, datum = yield) , 可以产出值, 也可以不产出——如果 yield关键字后面没有表达式, 那么生成器产出 None。 协程可能会从调用方接收数据, 不过调用方把数据提供给协程使用的是 .send(datum) 方法, 而不是 next(...) 函数。 通常, 调用方会把值推送给协程。
        yield 关键字甚至还可以不接收或传出数据。 不管数据如何流动, yield 都是一种流程控制工具, 使用它可以实现协作式多任务: 协程可以把控制器让步给中心调度程序, 从而激活其他的协程。从根本上把 yield 视作控制流程的方式, 这样就好理解协程了。
 

 一、生成器如何进化成协程

在Python2.5之后,  yield 关键字可以在表达式中使用,yield使用next调用下一条数据,生成器的调用方使用send方法发送数据,发送的数据会成为生成器函数 中yield的值。因此,生成器可以当做协程

协程是一个过程,这个过程与调用方协作,产出由调用方提供的值。

二、用作协程的生成器的基本行为

①示例:

def simple_coroutine():  #
    print('-> coroutine started')
    x = yield  #
    print('-> coroutine received:', x)

 
my_coro = simple_coroutine()  #创建生成器对象
next(my_coro)  #预激函数,激活协程。也可以写作my_ceo.send(None)
my_coro.send(42) #调用这个方法,x会得到42的值,send()是接收数据并向下执行

执行结果:

②产出两个值的协程

协程有四个状态:

协程可以身处四个状态中的一个。 当前状态可以使用inspect.getgeneratorstate(...) 函数确定, 该函数会返回下述字
符串中的一个。

'GEN_CREATED'

       等待开始执行。

'GEN_RUNNING'

        解释器正在执行。

'GEN_SUSPENDED'

         在 yield 表达式处暂停。

'GEN_CLOSED'

         执行结束。
 

def simple_coro2(a):
    print('-> Started: a =', a)
    b = yield a
    print('-> Received: b =', b)
    c = yield a + b
    print('-> Received: c =', c)


my_coro2 = simple_coro2(14)
from inspect import getgeneratorstate

print(getgeneratorstate(my_coro2))
print(next(my_coro2))
print(getgeneratorstate(my_coro2))
print(my_coro2.send(28))
print(my_coro2.send(99))

示例的三个执行阶段。

(1) 调用 next(my_coro2), 打印第一个消息, 然后执行 yield a, 产出数字 14。
(2) 调用 my_coro2.send(28), 把 28 赋值给 b, 打印第二个消息, 然后执行 yield a + b, 产出数字 42。
(3) 调用 my_coro2.send(99), 把 99 赋值给 c, 打印第三个消息, 协程终止。

每次执行都是以yield结束,产出值,而且也以yield开始,把send中的值赋给变量。

三、预激协程的装饰器

'''
使用协程之前必须预激,这一步容易忘记,为了避免忘记,可以使用一个特殊的装饰器
'''
from functools import wraps


def coroutine(func):
    @wraps(func)
    def inner(*args, **kwargs):
        gen = func(*args, **kwargs) #生成实例
        next(gen) #预激
        return gen

    return inner

使用装饰器

@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()
print(coro_avg.send(10)) #可以不用预激函数,直接使用send

四、终止协程(.close)和异常处理(.throw)

协程中未处理的异常会向上冒泡,传给next函数或send方法的调用方。

generator.throw(exc_type[, exc_value[, traceback]])

        致使生成器在暂停的 yield 表达式处抛出指定的异常。 如果生成器处理了抛出的异常, 代码会向前执行到下一个 yield 表达式, 而产出的值会成为调用 generator.throw 方法得到的返回值。 如果生成器没有处理抛出的异常, 异常会向上冒泡, 传到调用方的上下文中。
generator.close()
        致使生成器在暂停的 yield 表达式处抛出 GeneratorExit 异常。如果生成器没有处理这个异常, 或者抛出了 StopIteration 异常(通常是指运行到结尾) , 调用方不会报错。 如果收到 GeneratorExit 异常, 生成器一定不能产出值, 否则解释器会抛出 RuntimeError 异常。生成器抛出的其他异常会向上冒泡, 传给调用方。

示例

"""定义的异常类型。 """
class DemoException(Exception):
    pass


def demo_exc_handling():
    print('coroutine started')
    while True:
        try:
            x = yield
        except DemoException:  # 处理DemoException异常
            print('*** DemoException handled. Continuing...')
        else:
            print('coroutine received: {!r}'.format(x))

    raise RuntimeError('This line should never run.')  # 这一行永远不会执行。因为只有未处理的异常才会中止那个无限循环, 而一旦出现未处理的异常, 协程会立即终止。


exc_coro = demo_exc_handling()
next(exc_coro)
exc_coro.send(11)
exc_coro.send(22)
exc_coro.throw(DemoException)  # 把异常传入协程,协程可以处理这个异常,不会终止

# exc_coro.throw(ZeroDivisionError) #如果传入的异常没有处理,协程会终止

exc_coro.close()  # 关闭协程,

from inspect import getgeneratorstate

print(getgeneratorstate(exc_coro))  # 协程当前状态 GEN_CLOSED
exc_coro.send(22)  # 协程已经终止。

五、让协程返回值

某些协程不会产出值,而是在最后返回一个值。

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)  #返回一个nametuple。
avg=averager()
next(avg)
avg.send(10)
avg.send(30)
avg.send(40)
'''
发送 None 会终止循环, 导致协程结束, 返回结果。生成
器对象会抛出 StopIteration 异常。异常对象的value属性保存着返回的值
return 表达式的值会偷偷传给调用方, 赋值给StopIteration异常的一个属性。
'''
# avg.send(None)
#获取返回的值
try:
    avg.send(None)
except StopIteration as e:
    print(e.value)

六、使用yield from

        yield from 结构会在内部自动捕获StopIteration 异常。 这种处理方式与 for 循环处理 StopIteration异常的方式一样: 循环机制使用用户易于理解的方式处理异常。 对yield from 结构来说, 解释器不仅会捕获 StopIteration 异常, 还会把 value 属性的值变成 yield from 表达式的值。

①yield from可用于简化for循环中的yield表达式

def gen():
    for c in 'ab':
        yield c
    for i in range(2):
        yield i
print(list(gen()))
可改写为:
def gen():
    yield from 'ab'
    yield from  range(2)
print(list(gen()))

yield from x 表达式对 x 对象所做的第一件事是, 调用 iter(x), 从中获取迭代器。 因此, x 可以是任何可迭代的对象。

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

        yield from 的主要功能是打开双向通道, 把最外层的调用方与最内层的子生成器连接起来, 这样二者可以直接发送和产出值, 还可以直接传入异常, 而不用在位于中间的协程中添加大量处理异常的样板代码。

术语准备

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

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

示例

final_result = {}


def middle(key):
    while True:
        final_result[key] = yield from sales_sum(key)
        '''
        middle 发送的每个值都会经由 yield from 处理,sales_sum 实例。 middle 会在 yield from 表达式处暂停, 等待
        sales_sum 实例处理客户端发来的值。 sales_sum 实例运行完毕后, 返回的值绑定到 final_result[key] 上。
         while 循环会不断创建 sales_sum 实例, 处理更多的值。
        '''
        print(key + "销量统计完成!!.")


def sales_sum(pro_name):
    total = 0
    nums = []
    while True:
        x = yield
        print(pro_name + "销量: ", x)
        if not x:  # 终止条件,必须这么做,否则会永远阻塞
            break
        total += x
        nums.append(x)
    return total, nums


def main():
    data_sets = {
        "mingbo牌面膜": [1200, 1500, 3000],
        "mingbo牌手机": [28, 55, 98, 108],
        "mingbo牌大衣": [280, 560, 778, 70],
    }
    for key, data_set in data_sets.items():
        print("start key:", key)
        m = middle(key)
        m.send(None)  # 预激middle协程
        for value in data_set:  # 把各个value值传入middle,传入的值最终到达sales_sum函数中的x=yield那行,middle不知道传入的值是什么
            m.send(value)  # 给协程传递每一组的值
        m.send(None)  # 把None传给middle,最终进入sales_sum,x=None,导致当前的sales_sum实例结束,也让middle继续运行,再创建一个sales_sum实例,处理下一组值。
    print("final_result:", final_result)


if __name__ == '__main__':
    main()

调用方:main

子生成器:middle

委派生成器:sales_sum

示例展示了 yield from 结构最简单的用法, 只有一个委派生成器和一个子生成器。 因为委派生成器相当于管道所以可以把任意数量个委派生成器连接在一起: 一个委派生成器使用 yield from 调用一个子生成器, 而那个子生成器本身也是委派生成器, 使用 yield from 调用另一个子生成器, 以此类推。 最终, 这个链条要以一个只使用 yield表达式的简单生成器结束;
 

我的理解:

main是调用方,middle是中间商,sales_sum是工人的。main要使用中间商的服务,中间商又要找工人,在工人干完工作之前,中间商一直在等待工人的结果。在工人干完之后,中间商可以给出结果了。然后调用方再发布新的任务。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值