我有一个非异步函数和一个异步装饰函数。有什么方法可以用异步装饰器来装饰非异步函数吗?
async def dec():
# decorator body
@dec
def my_func():
# function body
由于在装饰器中,你在它的body里面定义了包装函数,所以可以这样工作。
def makeasync(func):
async def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
所以,被装饰的函数会被看作是一个async函数,并且可以被等待。然而,如果原函数在执行过程中被阻塞,无论是CPU块,还是IO块,都会使异步循环停滞,破坏了最初使用异步代码的优势。
更有用的是一种机制,它将在一个单独的线程中运行你的原始函数,甚至是一个单独的进程 (对于 CPU 绑定的函数)--你猜怎么着,Python 的 asyncio 确实有这样的效用--即 run_in_executor
loop方法允许你对一个非async函数进行规范的函数调用,并等待其结果。https:/docs.python.org3libraryasyncio-eventloop.html#asyncio.loop.run_in_executor。
如果你想把它作为一个装饰器,这几乎是微不足道的。
def makeasync(func):
async def wrapper(*args, **kwargs):
loop = asyncio.get_running_loop()
return await loop.run_in_executor (None, func, *args, **kwargs)
return wrapper
(默认情况下,asyncio会实例化一个concurrent.future.ThreadPoolExecutor--如果你的函数是IO绑定的,这是很好的--如果你不想要默认设置,你可以有一个单独的执行器来配合装饰器)
TL;DR装饰者的字面定义为 async def
不工作。我们完全可以从装饰器中产生异步函数,并且 jsbueno的回答 涵盖了这一点,但你必须使用一个普通的 def
为装饰者本身。本答案的其余部分将研究当你尝试使用 async def
来定义一个装饰器。
你所描述的在语法上是有效的,但运行时却没有意义。考虑到一个装饰符,如:
@dec
def my_func():
# ...
...是句法上的糖。
def my_func():
# ...
my_func = dec(my_func)
很明显 dec
必须接受一个函数参数,就像所有的装饰器一样。但这还不够:因为 dec
是一个异步函数,调用它产生一个 冠词对象. 这个对象可以被等待,但不能被调用。换句话说,这两种方法都不行。
foo = my_func() # TypeError: 'coroutine' object is not callable
foo = await my_func() # TypeError: 'coroutine' object is not callable
什么 会 工作是 await my_func
- 但这只是执行装饰器。通常装饰器是在顶层执行的,并且希望返回一个将被调用的函数,而不是被装饰的函数。这个装饰器不能这样工作,而且还存在其他问题。
另一个问题是 await my_func
只用一次,因为 await
会耗尽coroutine对象。通常这不是一个问题,因为你的 await
新创建的冠词对象,如 await bla()
. 如果你试过 x = bla(); await x; await x
第二项 await
也会失败。
总之,在当前的 Python async def
不能用于定义装饰器。