伪代码

问题描述 投票:-1回答:2

我有一个非异步函数和一个异步装饰函数。有什么方法可以用异步装饰器来装饰非异步函数吗?

async def dec():
    # decorator body

@dec
def my_func():
    # function body
python python-asyncio python-decorators coroutine
2个回答
0
投票

由于在装饰器中,你在它的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绑定的,这是很好的--如果你不想要默认设置,你可以有一个单独的执行器来配合装饰器)


0
投票

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 不能用于定义装饰器。

© www.soinside.com 2019 - 2024. All rights reserved.