【问题标题】:Python coroutinePython协程
【发布时间】:2020-08-28 12:41:48
【问题描述】:

我有一个非异步函数和一个异步装饰器函数。有没有办法用异步装饰器来装饰非异步函数?

async def dec():
    # decorator body

@dec
def my_func():
    # function body

【问题讨论】:

  • 当然是的.. 但是 my_func 的调用者必须同样是异步的,并使用 await 调用来获取结果。
  • @smassey 它并没有真正起作用,请参阅我的答案以进行详细说明。 jsbueno 的回答回答了 OP 可能打算提出的问题。

标签: python python-asyncio python-decorators coroutine


【解决方案1】:

由于在装饰器中,你在它的主体中定义了包装函数,它可以这样工作:

def makeasync(func):
    async def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

因此,装饰函数看起来像一个异步函数,可以等待。但是,如果原始 func 在执行中阻塞,无论是 CPU 阻塞还是 I/O 阻塞,它都会停止异步循环,并首先破坏异步代码的任何优势。

更有用的是一种机制,它可以在单独的线程,甚至是单独的进程(对于 CPU 绑定函数)中运行您的原始函数 - 而且,猜猜看,Python 的 asyncio 确实有这样一个实用程序 - run_in_executor loop 方法允许您指定对非异步函数的函数调用,并等待其结果: https://docs.python.org/3/library/asyncio-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 - 如果你的函数是 I/O 绑定的,这很好 - 如果你不想要默认设置,你可以有一个单独的执行器与装饰器一起使用)

【讨论】:

    【解决方案2】:

    TL;DR 定义为async def 的装饰器不起作用。从装饰器生成异步函数是完全可能的,jsbueno's answer 涵盖了这一点,但你必须为装饰器本身使用常规的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 会耗尽协程对象。通常这不是问题,因为您await 是一个新创建的协程对象,如await bla()。如果你尝试x = bla(); await x; await x,第二个await 也会失败。

    总之,在当前的 Python 中,async def 不能用于定义装饰器。

    【讨论】:

    • 装饰器中的 "def" 和装饰器内包装器中的 "async def" - 这是您所面临困境的关键(它会起作用,但还有其他问题 - 在我的回答中有详细说明)
    • @jsbueno 当然可以,但答案是指问题中描述的情况,其中有async def 装饰器。我确实是按字面意思理解的(回应那个特定 xyy),但考虑一下 很有趣。
    猜你喜欢
    • 2017-07-26
    • 1970-01-01
    • 1970-01-01
    • 2020-07-23
    • 2017-06-15
    • 2018-03-06
    • 2020-12-29
    • 2017-09-02
    相关资源
    最近更新 更多