乐趣区

关于人工智能:Python基础python协程

从概念上来说,咱们都晓得多过程和多线程,而协程其实是在单线程中实现多并发。从句法上看,协程与生成器相似,都是定义体中蕴含 yield 关键字的函数。区别在于协程的 yield 通常呈现在表达式的左边:​​datum = yield​​。这一下就让初学者霎时感觉 yield 关键字不香了,原本认为 yield 就是简简单单的暂停执行棘手返回个值,后果还能放左边?

从生成器到协程
先看一个可能是协程最简略的应用示例:

>>> def simple_coroutine():
...     print("-> coroutine started")
...     x = yield
...     print("-> coroutine received:", x)
...     
>>> my_coro = simple_coroutine()
>>> my_coro
<generator object simple_coroutine at 0x0000019A681F27B0>
>>> next(my_coro)
-> coroutine started
>>> my_coro.send(42)
-> coroutine received: 42
Traceback (most recent call last):
  File "<input>", line 1, in <module>
StopIteration

之所以 yield 能够放左边,是因为协程能够接管调用方应用​​.send()​​推送的值。

yield 放在左边当前,它的左边还能再放个表达式,请看上面这个例子:

def simple_coro2(a):
    b = yield a
    c = yield a + b

my_coro2 = simple_coro2(14)
next(my_coro2)
my_coro2.send(28)
my_coro2.send(99)

执行过程是:

调用 next(my_coro2),执行 yield a,产出 14。
调用 my_coro2.send(28),把 28 赋值给 b,而后执行 yield a + b,产出 42。
调用 my_coro2.send(99),把 99 赋值给 c,协程终止。

由此得出结论,对于 b = yield a 这行代码来说,= 左边的代码在赋值之前执行。

在示例中,须要先调用​​next(my_coro)​​启动生成器,让程序在 yield 语句处暂停,而后才能够发送数据。这是因为协程有四种状态:

‘GEN_CREATED’ 期待开始执行
‘GEN_RUNNING’ 解释器正在执行
‘GEN_SUSPENDED’ 在 yield 表达式处暂停
‘GEN_CLOSED’ 执行完结

只有在 GEN_SUSPENDED 状态能力发送数据,提前做的这一步叫做预激,既能够调用​​next(my_coro)​​​预激,也能够调用​​my_coro.send(None)​​预激,成果一样。

预激协程
协程必须预激能力应用,也就是 send 前,先调用 next,让协程处于 GEN_SUSPENDED 状态。然而这件事常常会遗记。为了防止遗记,能够定义一个预激装璜器,比方:

from functools import wraps

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

但实际上 Python 给出了一个更优雅的形式,叫做 yield from,它会主动预激协程。

自定义预激装璜器和 yield from 是不兼容的。

yield from
yield from 相当于其余语言中的 await 关键字,作用是:在生成器 gen 中应用 yield from subgen()时,subgen 会取得控制权,把产出的值传给 gen 的调用方,即调用方能够间接管制 subgen。与此同时,gen 会阻塞,期待 subgen 终止。

yield from 能够用来简化 for 循环中的 yield:

for c in "AB":
    yield c
yield from "AB"

yield from x 表达式对 x 做的第一件事就是,调用 iter(x),从中获取迭代器。

但 yield from 的作用远不止于此,它更重要的作用是关上双向通道。如下图所示:


这个图信息量很大,很难了解。

首先要了解这 3 个概念:调用方、委派生成器、子生成器。

调用方
说白了就是 main 函数,也就是家喻户晓的程序入口 main 函数。

​# the client code, a.k.a. the caller def main(data): # <8> results = {} for key, values in data.items(): group = grouper(results, key) # <9> next(group) # <10> for value in values: group.send(value) # <11> group.send(None) # important! <12> # print(results) # uncomment to debug report(results)​

委派生成器
就是蕴含了 yield from 语句的函数,也就是协程。

​# the delegating generator def grouper(results, key): # <5> while True: # <6> results[key] = yield from averager() # <7>​

子生成器
就是 yield from 语句左边跟着的子协程。

​​# the subgenerator def averager(): # <1> total = 0.0 count = 0 average = None while True: term = yield # <2> if term is None: # <3> break total += term count += 1 average = total/count return Result(count, average) # <4>​ ​

这比术语看着难受多了。

而后是 5 条线:send、yield、throw、StopIteration、close。

send
协程在 yield from 表达式处暂停时,main 函数能够通过 yield from 表达式把数据发给 yield from 语句左边跟着的子协程。
yield
yield from 语句左边跟着的子协程再把产出的值通过 yield from 表达式发给 main 函数。
throw
main 函数通过​​group.send(None)​​,传入一个 None 值,让 yield from 语句左边跟着的子协程的 while 循环终止,这样控制权才会交回协程,能力继续执行,否则会始终暂在 yield from 语句暂停。
StopIteration
yield from 语句左边跟着的生成器函数返回之后,解释器会抛出 StopIteration 异样。并把返回值附加到异样对象上,此时协程会复原。
close
main 函数执行完当前,会调用 close()办法退出协程。

大体流程搞清楚了,更多的技术细节就不持续钻研了,有工夫的话,在当前的 Python 原理系列中再学习吧。

yield from 常常与 Python3.4 规范库里的​​@asyncio.coroutine​​装璜器联合应用。

协程用作累加器
这是协程的常见用处,代码如下:

def averager():
    total = 0.0
    count = 0
    average = None
    while True:  # <1>
        term = yield average  # <2>
        total += term
        count += 1
        average = total/count

协程实现并发
这里例子有点简单​

外围代码片段是:

# BEGIN TAXI_PROCESS
def taxi_process(ident, trips, start_time=0):  # <1>
    """Yield to simulator issuing event at each state change"""
    time = yield Event(start_time, ident, 'leave garage')  # <2>
    for i in range(trips):  # <3>
        time = yield Event(time, ident, 'pick up passenger')  # <4>
        time = yield Event(time, ident, 'drop off passenger')  # <5>

    yield Event(time, ident, 'going home')  # <6>
    # end of taxi process # <7>
# END TAXI_PROCESS
def main(end_time=DEFAULT_END_TIME, num_taxis=DEFAULT_NUMBER_OF_TAXIS,
         seed=None):
    """Initialize random generator, build procs and run simulation"""
    if seed is not None:
        random.seed(seed)  # get reproducible results

    taxis = {i: taxi_process(i, (i+1)*2, i*DEPARTURE_INTERVAL)
             for i in range(num_taxis)}
    sim = Simulator(taxis)
    sim.run(end_time)

这个示例阐明了如何在一个主循环中处理事件,以及如何通过发送数据驱动协程。这是 asyncio 包底层的根本思维。应用协程代替线程和回调,实现并发。

退出移动版