PEP 0492添加了新的__await__
魔术方法。实现此方法的对象变为类似future的对象,可以使用await
等待。很清楚:
import asyncio
class Waiting:
def __await__(self):
yield from asyncio.sleep(2)
print('ok')
async def main():
await Waiting()
if __name__ == "__main__":
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
好的,但如果我想调用一些async def
定义的函数而不是asyncio.sleep
怎么办?我无法使用await
,因为__await__
不是async
函数,我无法使用yield from
,因为原生协同程序需要await
表达式:
async def new_sleep():
await asyncio.sleep(2)
class Waiting:
def __await__(self):
yield from new_sleep() # this is TypeError
await new_sleep() # this is SyntaxError
print('ok')
我该如何解决?
答案 0 :(得分:30)
使用直接__await__()
电话:
async def new_sleep():
await asyncio.sleep(2)
class Waiting:
def __await__(self):
return new_sleep().__await__()
解决方案由Yury Selivanov(PEP 492的作者)推荐aioodbc library
答案 1 :(得分:13)
简短版本:await foo
可以替换为yield from foo.__await__()
结合其他答案的所有想法 -
在最简单的情况下,只是委托另一个等待的作品:
def __await__(self):
return new_sleep().__await__()
这是有效的,因为__await__
方法返回一个迭代器(参见PEP 492),所以返回另一个__await__
的迭代器就可以了。
这当然意味着我们无法改变原先等待的暂停行为。更一般的方法是镜像await
关键字并使用yield from
- 这可以让我们将多个等待的迭代器组合成一个:
def __await__(self):
# theoretically possible, but not useful for my example:
#yield from something_else_first().__await__()
yield from new_sleep().__await__()
这里有一个问题:这与第一个变体完全不同! yield from
是一个表达式,所以与以前完全相同,我们还需要返回该值:
def __await__(self):
return (yield from new_sleep().__await__())
这直接反映了我们如何使用await
语法编写正确的委派:
return await new_sleep()
额外位 - 这两者有什么区别?
def __await__(self):
do_something_synchronously()
return new_sleep().__await__()
def __await__(self):
do_something_synchronously()
return (yield from new_sleep().__await__())
第一个变量是一个普通函数:当你调用它时,执行do_...
并返回一个迭代器。第二个是发电机功能;调用它根本不执行任何代码!只有当第一次返回返回的迭代器时才会执行do_...
。这在以下方面有所不同,有点人为的情况:
def foo():
tmp = Waiting.__await__()
do_something()
yield from tmp
答案 2 :(得分:5)
我不明白为什么我不能从__await__
内的原生协程中获得收益,但看起来可以从生成器协程中生成{ 生成器协程中的{1}}和来自本地协程。它有效:
__await__
答案 3 :(得分:5)
要等待__await__
函数内部,请使用以下代码:
async def new_sleep():
await asyncio.sleep(1)
class Waiting:
def __await__(self):
yield from new_sleep().__await__()
print('first sleep')
yield from new_sleep().__await__()
print('second sleep')
return 'done'
答案 4 :(得分:2)
使用装饰器。
def chain__await__(f):
return lambda *args, **kwargs: f(*args, **kwargs).__await__()
然后将__await__
写为本地协程。
async def new_sleep():
await asyncio.sleep(2)
class Waiting:
@chain__await__
async def __await__(self):
return await new_sleep()
答案 5 :(得分:0)
您的原始代码在Python 3.6中运行良好。
其他答案中的修复程序令人印象深刻,我什至不敢相信其中的某些功能可以工作(但是它们确实可以!),但是我担心如果asyncio
模型不断变化,这些修复程序将停止工作。
我有一个简单的修复程序,该修复程序可以在3.7上运行,而无需使用包装程序
:import asyncio
class Waiting:
def __await__(self):
yield from asyncio.sleep(2).__await__()
print('ok')
async def main():
await Waiting()
if __name__ == "__main__":
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
与原始代码的唯一区别是将.__await__()
添加到asyncio.sleep(2)
-即使没有包装。
您还可以将sync_await
的包装器await
包围在您要__await__
的生成器中,就像这样:
import asyncio
def sync_await(gen):
if hasattr(gen, '__await__'):
# 3.7, and user defined coroutines in 3.6
print('yield from gen.__await__()')
return (yield from gen.__await__())
else:
# 3.6, only native coroutines like asyncio.sleep()
print('yield from gen')
return (yield from gen)
class Waiting:
def __await__(self):
yield from sync_await(asyncio.sleep(2))
print('ok')
async def main():
await Waiting()
if __name__ == "__main__":
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
注意-包装器不执行return (yield from ...)
,但是yield from
-简单的生成器迭代器委派。