Python协程——流畅的Python

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

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

>>> 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
>>> getgeneratorstate(my_coro2)
'GEN_CREATED'                             # inspect.getgeneratorstate 函数指明 GEN_CREATED 状态.
>>> next(my_coro2)                        # 激活协程,协程向前执行到第一个 yield 表达式,
-> Started: a = 14                        # 打印 
14                                        # 产出 a 的值
>>> getgeneratorstate(my_coro2)
'GEN_SUSPENDED'                           # GEN_SUSPENDED 状态
>>> my_coro2.send(28)                     # 将数字 28 发送给暂停的协程;计算 yield 表达式,得到 28,
-> Received: b = 28                       # 并将其绑定给 b. 打印
42                                        # 产出 a + b,然后暂停,等待为 c 赋值.
>>> my_coro2.send(99)                     # 将数字 99 发送给暂停的协程;计算 yield 表达式,得到 99,
-> Received: c = 99                       # 并将其绑定给 c. 打印
Traceback (most recent call last):        # 协程终止, 生成器对象抛出 StopIteration 异常.
  File "<stdin>", line 1, in <module>
StopIteration
>>> getgeneratorstate(my_coro2)           # 指明协程处于 GEN_CLOSED 状态.
'GEN_CLOSED'    

关键在于协程在yield关键字所在的位置暂停执行:在赋值语句中,=右边的代码在赋值之前执行。因此,对于b = yield a这行代码来说,等到客户端代码再次激活协程时才会设定b的值。这便是异步编程中yield的作用

1.2、协程使用示例:移动平均值的计算

1.2.1 类方法实现

>>> class Averager():
...     
...     def __init__(self):
...         self.series = []
...     
...     def __call__(self, new_value):
...         self.series.append(new_value)
...         total = sum(self.series)
...         return total/len(self.series)
... 
>>> avg = Averager()
>>> avg(10)
10.0
>>> avg(11)
10.5
>>> avg(12)
11.0

1.2.2 高阶函数闭包实现

实现一:

>>> def make_averager():
...     series = []                   # series 是 make_averager 函数的局部变量
...     
...     def averager(new_value):
...         series.append(new_value)  # series 是 自由变量(未在本地作用域中绑定的变量)
...         total = sum(series)
...         return total/len(series)
...     
...     return averager
... 
>>> avg = make_averager()
>>> avg(10)
10.0
>>> avg(11)
10.5
>>> avg(12)
11.0
>>> avg.__code__.co_varnames         # avg 的局部变量
('new_value', 'total')
>>> avg.__code__.co_freevars         # avg 的自由变量
('series',)
>>> avg.__closure__                  # series 的绑定在返回的 avg 函数的 __closure__ 属性中。
                                     # avg.__closure__ 中的各个元素对应于 avg.__code__.co_freevars
                                     # 中的一个名称。
(<cell at 0x7f06f930ac18: list object at 0x7f06f89d1e08>,)
>>> avg.__closure__[0].cell_contents # 这些元素是 cell 对象,有个 cell_contents 属性,保存这真正的值。
[10, 11, 12]

实现二:

>>> def make_averager():
...     total = 0
...     count = 0
...     
...     def averager(new_value):
...         nonlocal count, total    # nonlocal 声明,把变量标记为自由变量。
...         count += 1               # 为nonlocal声明的变量赋予新值,闭包中保存的绑定会更新。
...         total += new_value
...         return total/count
...     
...     return averager
... 
>>> avg = make_averager()
>>> avg(10)
10.0
>>> avg(11)
10.5
>>> avg(12)
11.0
>>> avg.__code__.co_varnames
('new_value',)
>>> avg.__code__.co_freevars
('count', 'total')
>>> avg.__closure__
(<cell at 0x7f06f930ac18: int object at 0xa69e40>, <cell at 0x7f06f897b348: int object at 0xa6a200>)
>>> avg.__closure__[0].cell_contents
3
>>> avg.__closure__[1].cell_contents
33
>>> 

averager()函数中使用了nonlocal声明。因为当为自由变量赋值时,会隐式创建局部变量,例如:count = count + 1,会隐式创建局部变量count。这样,count就不是自由变量,也不会保存在闭包中了。使用nonlocal声明的变量可以避免上述问题。

1.2.3 协程方法实现

实现一:无返回值

>>> 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
>>> getgeneratorstate(coro_avg)
'GEN_CREATED'
>>> next(coro_avg)
>>> coro_avg.send(10)
10.0
>>> coro_avg.send(11)
10.5
>>> coro_avg.close()
>>> getgeneratorstate(coro_avg)
'GEN_CLOSED'

实现二:有返回值

from collections import namedtuple
from functools import wraps

Result = namedtuple('Result', 'count average')


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
        if term is None:
            break
        total += term
        count += 1
        average = total/count
    return Result(count, average)
"""
>>> coro_avg = averager()
>>> from inspect import getgeneratorstate
>>> getgeneratorstate(coro_avg)
'GEN_SUSPENDED'
>>> coro_avg.send(10)
10.0
>>> coro_avg.send(20)
15.0
>>> coro_avg.send(30)
20.0
>>> coro_avg.send(None)                      # 发送 None 终止循环,导致协程结束,返回结果
Traceback (most recent call last):           # 生成器对象会抛出 StopIteration 异常。
  File "<stdin>", line 1, in <module>        # 异常对象的 value 属性保存着返回值。
StopIteration: Result(count=3, average=20.0) #

"""
"""
>>> coro_avg = averager()
>>> getgeneratorstate(coro_avg)
'GEN_SUSPENDED'
>>> coro_avg.send(10)
10.0
>>> coro_avg.send(30)
20.0
>>> coro_avg.send(6.5)
15.5
>>> try:
...     coro_avg.send(None)
... except StopIteration as exc:
...     result = exc.value            # 通过捕获 StopIteration 异常,获取 averager 返回值
... 
>>> result
Result(count=3, average=15.5) 

"""

获取协程返回值要绕一个圈子。yield from结构会在内部自动捕获StopIteration异常,还会把value属性的值变成yield from表达式的值。

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)


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.41, 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],
}


# 委派生成器
def grouper(results, key):
    while True:
        results[key] = yield from averager() # grouper 在该处暂停,等待 averager 实例处理客户端发来的值。
                                             # averger 运行完毕后(客户端发送None),返回的值绑定到
                                             # results[key] 上
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
        ))

# 调用方
def main(data):
    results = {}
    for key, values in data.items():
        # group是调用grouper函数得到的生成器对象。group作为协程使用
        group = grouper(results, key)
        next(group)
        for value in values:
            # 把各个value传给grouper。传入的值最终到达averager函数中term = yield那一行
            group.send(value)    # 将 value 传送给 grouper。 传入的值最终到达 averager 函数
                                 # term = yield 行,grouper 永远不知道传入的值是什么
        # print(results)
        group.send(None)         # 必须发送 None ,否则 averager 子生成器永远不会终止,
                                 # 委派生成器 group 永远不会再次激活, 
                                 # 因此永远不会为 results[key] 赋值。当外层 for 循环
                                 # 重新迭代时会新建一个 grouper 实例并绑定到 group 变量上。
                                 # 前一个 grouper 实例以及它创建的尚未终止的 averager 子
                                 # 生成器实例被垃圾回收程序回收。
    # print(results)
    report(results)


if __name__ == '__main__':
    main(data)
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值