Fluent Python

协程

2018-01-20  本文已影响8人  一块大番薯
>>> def average():
...     count = 0
...     average = None
...     total = 0
...     while True:
...             term = yield average
...             total += term
...             count += 1
...             average = total / count
...
>>> coro = average()
>>> next(coro)
>>> coro.send(10)
10.0
>>> coro.send(20)
15.0
>>> coro.send(30)
20.0
>>>

只有调用方在协程上调用 close() 方法,或没有协程引用而被垃圾回收时,这个协程才会停止。

预激协程的装饰器

from functools import wraps

def coroutine(func):
    @wraps(func)
    def primer(*args, **kwargs):
        gen = func(*args, **kwargs)
        next(gen)
        return gen
    return primer

装饰器也不过如此。
coroutine 是一个装饰器,最终返回 primer 函数,说明想用 primer 代替传入的 func。
所以,编写 primer 就是重新写 func 函数,但是可以调用之前的 func。

from coroutil import coroutine

@coroutine
def averager():
    average = None
    total = 0
    count = 0
    while True:
        term = yield average
        total += term
        count += 1
        average = total / count
>>> coro = averager()
>>> coro.send(10)
10.0
>>> coro.send(20)
15.0
>>> coro.close()
>>> from inspect import getgeneratorstate
>>> getgeneratorstate(coro)
'GEN_CLOSED'
>>>

让协程返回值

from collections import namedtuple

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

def averager():
    count = 0
    average = None
    total = 0
    while True:
        term = yield
        if term is None:
            break    # 一定要有 break,才会执行 return 
        total += term
        count += 1
        average = total / count
    return Result(count, average)
>>> coro = averager()
>>> next(coro)
>>> coro.send(10)
>>> coro.send(20)
>>> coro.send(None)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration: Result(count=2, average=15.0)
>>>

发送 None 终止循环,协程结束。
同时生成器对象抛出 StopIteration 异常,异常对象的 value 属性保存返回的值。
更妥当的做法:

>>> coro = averager()
>>> next(coro)
>>> coro.send(10)
>>> coro.send(20)
>>> try:
...     coro.send(None)
... except StopIteration as exc:
...     result = exc.value
...
>>> result
Result(count=2, average=15.0)
>>>

使用 yield from(await)

作用是代替只有 yield 表达式的 for 循环。

>>> def gen():
...     for i in range(3):
...             yield i
...
>>> def gen2():
...     yield from range(3)
...
>>> list(gen())
[0, 1, 2]
>>> list(gen2())
[0, 1, 2]
>>>

链接可迭代对象(当然了,用 C 编写的 itertools.chain() 更好):

>>> def chain(*iterables):
...     for i in iterables:
...             yield from i    # yield from 相当于又一次循环
...
>>> list(chain('abc', range(3), 'xyz'))
['a', 'b', 'c', 0, 1, 2, 'x', 'y', 'z']
>>>
上一篇 下一篇

猜你喜欢

热点阅读