python异步编程
1 异步和类
__await__
协议,对象实现了该方法,那么该对象就是awaitable对象
1.1 yield from奇技淫巧
需要注意的是,在__await__方法里我选择了以一个while循环加yield的方式来构建生成器,而不是以yield from asyncio.sleep(self.duration)的方式去嵌套生成器。 如果是后者,解析器会弹出
TypeError: cannot 'yield from' a coroutine object in a non-coroutine generator
异常,因为从3.5开始Python刻意在语法上对await和yield from关键字作出了区分,协程不再能被yield from调用,而是必须await。不过实际上, 存在一个workaround,就是先用asyncio.Task包裹协程,再以yield from调用,以下代码可行,asyncio.gather同理。
class yieldfromSleep:
def __init__(self, duration):
self.duration = duration
def __await__(self):
start = time.perf_counter()
print(f'Start {type(self).__name__} at {start} s')
task = asyncio.create_task(asyncio.sleep(self.duration))
yield from task
print(f'Sleep for {time.perf_counter() - start} s')
await yieldfromSleep(1)
至此,一个简陋版的Python协程以类的形式被重新构造,而不需要用async def来定义协程函数。
1.2 异步构造函数
理清了协程的构造,接下来介绍如何以异步的方式构造一个类的实例。如果一个类实例的初始化需要从网络获取数据,且需要请求不止一个API,以异步的方式构造实例就能缩短时间,提升效率。
既然协程的异步都来自__await__方法,且该方法可以用yield from的形式来调用其他协程,那事情就简单了。__init__该怎么写还是怎么写, 不过把需要异步并发创建的属性搬到__await__里,在__await__里用yield from调用异步网络API,事情就解决了。例子如下
class DataClass:
def __init__(self, **kwargs):
self.local = kwargs
def __await__(self):
task = asyncio.create_task(aiohttp.ClientSession.request(url))
self.data = yield from task
return self
dataInstance = await DataClass(**kwargs)
其实现和上面的重构协程差不多,当DataClass(**kwargs)被await调用时,先用__init__方法构建实例,然后await会调用该实例的__await__方法, 剩下的实例属性则在__await__里被异步创建。不过要注意的是,单纯睡眠时__await__不用返回结果,而在这里需要返回self, 否则dataInstance是不会被赋值构建好的实例。
参考链接