您的位置:首页 > 编程语言 > Python开发

python协程

2019-02-05 23:30 10 查看
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://blog.csdn.net/qq_45608306/article/details/100571429

协程

定义:协程不是进程或线程,其执行过程更类似于子例程,或者说不带返回值的函数调用。

协程与线程:一个程序可以包含多个协程,可以对比与一个进程包含多个线程,因而下面我们来比较协程和线程。我们知道多个线程相对独立,有自己的上下文,切换受系统控制;而协程也相对独立,有自己的上下文,但是其切换由自己控制,由当前协程切换到其他协程由当前协程来控制。

协程与生成器:从句法上看,协程类似于生成器,都是定义体中包含yield关键字的函数。但协程中yield通常出现在表达式右边,可以产出值也可以不产出值--如果yield关键字后面没有表达式,那么生成器产出None。

把yield视作控制流程的方式,就容易理解协程。

协程的状态

一个简单协程示例:

[code]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()函数获取:

[code]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()函数激活协程才能将值发送给协程,否则报错。

[code]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()函数预激协程

或使用装饰器

[code]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。生成器抛出的其他异常会向上冒泡。

 

异常处理测试代码:

[code]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,没有异常:

[code]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协程,它会处理然后继续运行:

[code]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

尝试传入没有处理的异常:

[code]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块中:

[code]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))

7ff7
finally:
print('ending')

获取协程返回值

使用return直接返回值是可行的,但和普通函数不同,return语句的返回值会赋值给StopIteration异常的一个属性。

[code]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异常,获取返回值:

[code]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表达式:

[code]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字典中读取虚构的七年级男女学生的体重和身高,获取生成平均结果。

[code]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》

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: