我如何在类未来对象的 __await__ 中等待?
How can I await inside future-like object's __await__?
PEP 0492 添加了新的 __await__
魔法方法。实现此方法的对象成为 类未来对象 并且可以使用 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')
我该如何解决?
我不明白为什么我不能从 __await__
中的原生协程 中 yield,但看起来可以从生成器中 yield __await__
内的协程 和 生成器协程 内的原生协程 产生。有效:
async def new_sleep():
await asyncio.sleep(2)
class Waiting:
def __await__(self):
@asyncio.coroutine
def wrapper(coro):
return (yield from coro)
return (yield from wrapper(new_sleep()))
使用直接__await__()
调用:
async def new_sleep():
await asyncio.sleep(2)
class Waiting:
def __await__(self):
return new_sleep().__await__()
该解决方案由 Yury Selivanov(PEP 492) for aioodbc library
的作者推荐
要在 __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'
简短版本:await foo
可以替换为 yield from foo.__await__()
结合其他答案的所有想法 -
在最简单的情况下,只需委托给另一个等待的作品:
def __await__(self):
return new_sleep().__await__()
这是有效的,因为 __await__
方法 return 是一个迭代器(参见 PEP 492),所以 return 使用另一个 __await__
的迭代器是可以的.
当然,这意味着我们根本无法改变原始等待的挂起行为。更通用的方法是镜像 await
关键字并使用 yield from
- 这让我们可以将多个 awaitables 的迭代器合并为一个:
def __await__(self):
# theoretically possible, but not useful for my example:
#yield from something_else_first().__await__()
yield from new_sleep().__await__()
这里有一个要点:这与第一个变体不完全一样! yield from
是一个表达式,所以要和以前完全一样,我们还需要 return 该值:
def __await__(self):
return (yield from new_sleep().__await__())
这直接反映了我们如何使用 await
语法编写正确的委托:
return await new_sleep()
extra bit - 这两者有什么区别?
def __await__(self):
do_something_synchronously()
return new_sleep().__await__()
def __await__(self):
do_something_synchronously()
return (yield from new_sleep().__await__())
第一个变体是一个普通函数:当你调用它时,do_...
被执行并且一个迭代器 returned。第二个是生成器函数;调用它根本不会执行我们的任何代码!只有当 returned 迭代器第一次被 yield 时才会执行 do_...
。这在以下有点人为的情况下有所不同:
def foo():
tmp = Waiting.__await__()
do_something()
yield from tmp
使用装饰器。
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()
您的原始代码在 Python 3.6.
中运行良好
快速修复(12 个字符)
其他答案中的修复令人印象深刻,我什至不敢相信其中一些有效(但它们确实有效!),但我担心如果 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
- 简单的生成器迭代器委托。
PEP 0492 添加了新的 __await__
魔法方法。实现此方法的对象成为 类未来对象 并且可以使用 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')
我该如何解决?
我不明白为什么我不能从 __await__
中的原生协程 中 yield,但看起来可以从生成器中 yield __await__
内的协程 和 生成器协程 内的原生协程 产生。有效:
async def new_sleep():
await asyncio.sleep(2)
class Waiting:
def __await__(self):
@asyncio.coroutine
def wrapper(coro):
return (yield from coro)
return (yield from wrapper(new_sleep()))
使用直接__await__()
调用:
async def new_sleep():
await asyncio.sleep(2)
class Waiting:
def __await__(self):
return new_sleep().__await__()
该解决方案由 Yury Selivanov(PEP 492) for aioodbc library
的作者推荐要在 __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'
简短版本:await foo
可以替换为 yield from foo.__await__()
结合其他答案的所有想法 -
在最简单的情况下,只需委托给另一个等待的作品:
def __await__(self):
return new_sleep().__await__()
这是有效的,因为 __await__
方法 return 是一个迭代器(参见 PEP 492),所以 return 使用另一个 __await__
的迭代器是可以的.
当然,这意味着我们根本无法改变原始等待的挂起行为。更通用的方法是镜像 await
关键字并使用 yield from
- 这让我们可以将多个 awaitables 的迭代器合并为一个:
def __await__(self):
# theoretically possible, but not useful for my example:
#yield from something_else_first().__await__()
yield from new_sleep().__await__()
这里有一个要点:这与第一个变体不完全一样! yield from
是一个表达式,所以要和以前完全一样,我们还需要 return 该值:
def __await__(self):
return (yield from new_sleep().__await__())
这直接反映了我们如何使用 await
语法编写正确的委托:
return await new_sleep()
extra bit - 这两者有什么区别?
def __await__(self):
do_something_synchronously()
return new_sleep().__await__()
def __await__(self):
do_something_synchronously()
return (yield from new_sleep().__await__())
第一个变体是一个普通函数:当你调用它时,do_...
被执行并且一个迭代器 returned。第二个是生成器函数;调用它根本不会执行我们的任何代码!只有当 returned 迭代器第一次被 yield 时才会执行 do_...
。这在以下有点人为的情况下有所不同:
def foo():
tmp = Waiting.__await__()
do_something()
yield from tmp
使用装饰器。
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()
您的原始代码在 Python 3.6.
中运行良好快速修复(12 个字符)
其他答案中的修复令人印象深刻,我什至不敢相信其中一些有效(但它们确实有效!),但我担心如果 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
- 简单的生成器迭代器委托。