Decorator 如何创建一个可以包装协同程序或函数的Python装饰器?

Decorator 如何创建一个可以包装协同程序或函数的Python装饰器?,decorator,python-asyncio,Decorator,Python Asyncio,我正在尝试制作一个装饰器来包装协程或函数 我尝试的第一件事是包装器中的简单重复代码: def持续时间(func): @functools.wrapps(func) def包装(*args,**kwargs): 开始\u ts=time.time() 结果=函数(*args,**kwargs) dur=time.time()-开始 打印({}花费了{:.2}秒。格式(函数名、dur)) 返回结果 @functools.wrapps(func) async def async_包装(*args,**

我正在尝试制作一个装饰器来包装协程或函数

我尝试的第一件事是包装器中的简单重复代码:

def持续时间(func):
@functools.wrapps(func)
def包装(*args,**kwargs):
开始\u ts=time.time()
结果=函数(*args,**kwargs)
dur=time.time()-开始
打印({}花费了{:.2}秒。格式(函数名、dur))
返回结果
@functools.wrapps(func)
async def async_包装(*args,**kwargs):
开始\u ts=time.time()
结果=等待函数(*args,**kwargs)
dur=time.time()-开始
打印({}花费了{:.2}秒。格式(函数名、dur))
返回结果
如果asyncio.iscoroutinefunction(func):
返回异步包装器
其他:
返回包装器
这是可行的,但我希望避免代码重复,因为这并不比编写两个独立的装饰器好多少

然后我尝试使用类制作装饰器:

类同步持续时间:
定义初始化(自):
self.start\u ts=无
定义调用(self,func):
@functools.wrapps(func)
def sync_包装(*args,**kwargs):
自我设置(func、args、kwargs)
结果=函数(*args,**kwargs)
自拆卸(func、args、kwargs)
返回结果
@functools.wrapps(func)
async def async_包装(*args,**kwargs):
自我设置(func、args、kwargs)
结果=等待函数(*args,**kwargs)
自拆卸(func、args、kwargs)
返回结果
如果asyncio.iscoroutinefunction(func):
返回异步包装器
其他:
返回同步包装器
def设置(自身、功能、参数、kwargs):
self.start\u ts=time.time()
def拆卸(自身、功能、参数、kwargs):
dur=time.time()-self.start\u ts
打印({}花费了{:.2}秒。格式(函数名、dur))
这在某些情况下对我来说非常有效,但在这个解决方案中,我无法使用或try语句将函数放入
有什么方法可以在不复制代码的情况下创建装饰器吗?

也许您可以找到更好的方法,但是,例如,您可以将包装逻辑移动到某个上下文管理器以防止代码复制:

import asyncio
import functools
import time
from contextlib import contextmanager


def duration(func):
    @contextmanager
    def wrapping_logic():
        start_ts = time.time()
        yield
        dur = time.time() - start_ts
        print('{} took {:.2} seconds'.format(func.__name__, dur))

    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        if not asyncio.iscoroutinefunction(func):
            with wrapping_logic():
                return func(*args, **kwargs)
        else:
            async def tmp():
                with wrapping_logic():
                    return (await func(*args, **kwargs))
            return tmp()
    return wrapper

对我来说,@mikhail gerasimov接受的答案是不使用异步FastAPI方法(尽管它在FastAPI之外使用普通函数和协程函数)。然而,我在github上找到了一个使用fastapi方法的示例。改编(略)如下:

def持续时间(func):
异步定义帮助程序(func、*args、**kwargs):
如果asyncio.iscoroutinefunction(func):
print(f“此函数是一个协同程序:{func.\uuuuu name\uuuuu}”)
返回等待函数(*args,**kwargs)
其他:
打印(f“非协同程序:{func.\uuuu name\uuuuuu}”)
返回函数(*args,**kwargs)
@functools.wrapps(func)
异步def包装(*args,**kwargs):
开始\u ts=time.time()
结果=等待帮助程序(func、*args、**kwargs)
dur=time.time()-开始
打印({}花费了{:.2}秒。格式(函数名、dur))
返回结果
返回包装器
或者,如果要保留contextmanager,也可以执行以下操作:

def持续时间(func):
“”“可以使用协同程序或正常函数的装饰器”“”
@上下文管理器
def包装逻辑():
开始\u ts=time.time()
产量
dur=time.time()-开始
打印({}花费了{:.2}秒。格式(函数名、dur))
@functools.wrapps(func)
异步def包装(*args,**kwargs):
如果不是asyncio.iscoroutinefunction(func):
使用包装逻辑()
返回函数(*args,**kwargs)
其他:
使用包装逻辑()
返回(等待函数(*args,**kwargs))
返回包装器
这与公认的答案之间的差别不大。主要是我们只需要创建一个异步包装器,并等待该函数(如果该函数是一个协程)

在我的测试中,这个示例代码在修饰函数中的
try/except
块以及
语句中工作


我仍然不清楚为什么异步FastAPI方法的包装器需要是异步的。

您可以使用
time.monotonic()
而不是
time.time()
,以防止时间更新对系统的副作用。这对我来说适用于正常函数,但不适用于异步协程<代码>返回tmp()
引发异常。我想更改为
return tmp
可能会起作用,实际上这会删除异常,但它不会执行修饰函数。@Ben,你能提供一个不起作用的代码示例吗?我从上一条注释中发现了代码中的错误。在调用异步方法之前,我没有捕获事件循环。我在使用FastAPI时仍然遇到问题。我可以为异步fastapi方法创建一个装饰器。我可以为一个普通的fastapi函数创建一个装饰器。但是,当我尝试以您概述的方式组合它们时,我得到了
ValueError:[TypeError(“'coroutine'对象不可iterable”)、TypeError('vars()参数必须具有
attribute')]code:此解决方案的一个重要缺点是调用
aysncio.iscoroutinefunction()
在用这样的装饰器包装的协同程序上,总是返回
False
。由于
@coroutine
decorator已被弃用,最好的方法是像OP那样声明两个包装器,并使用上下文管理器对可能的内容进行分解。此解决方案的最大缺点是您必须始终使用
wait
调用包装函数。你不能再在同步代码中使用这个decorator了,这是我根据公认的答案创建的解决方案:想法是你必须使用