我正在尝试让装饰器包装协同程序或函数。
我尝试的第一件事是包装器中的简单重复代码:
def duration(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start_ts = time.time()
result = func(*args, **kwargs)
dur = time.time() - start_ts
print('{} took {:.2} seconds'.format(func.__name__, dur))
return result
@functools.wraps(func)
async def async_wrapper(*args, **kwargs):
start_ts = time.time()
result = await func(*args, **kwargs)
dur = time.time() - start_ts
print('{} took {:.2} seconds'.format(func.__name__, dur))
return result
if asyncio.iscoroutinefunction(func):
return async_wrapper
else:
return wrapper
这有效,但我想避免重复代码,因为这并不比编写两个单独的装饰器好。
然后我尝试使用类创建装饰器:
class SyncAsyncDuration:
def __init__(self):
self.start_ts = None
def __call__(self, func):
@functools.wraps(func)
def sync_wrapper(*args, **kwargs):
self.setup(func, args, kwargs)
result = func(*args, **kwargs)
self.teardown(func, args, kwargs)
return result
@functools.wraps(func)
async def async_wrapper(*args, **kwargs):
self.setup(func, args, kwargs)
result = await func(*args, **kwargs)
self.teardown(func, args, kwargs)
return result
if asyncio.iscoroutinefunction(func):
return async_wrapper
else:
return sync_wrapper
def setup(self, func, args, kwargs):
self.start_ts = time.time()
def teardown(self, func, args, kwargs):
dur = time.time() - self.start_ts
print('{} took {:.2} seconds'.format(func.__name__, dur))
在某些情况下,这对我很有效,但在此解决方案中,我无法在中添加或尝试语句。 有没有办法可以在不重复代码的情况下创建装饰器?
答案 0 :(得分:2)
可能您可以找到更好的方法来做到这一点,但是,例如,您可以将包装逻辑移动到某个上下文管理器以防止代码重复:
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
答案 1 :(得分:1)
对我而言,@ mikhail-gerasimov接受的答案不适用于异步FastAPI方法(尽管它确实适用于FastAPI之外的常规和协程函数)。但是,我在github上发现了{/ 3}}的示例,该示例可以使用fastapi方法工作。改编(略)如下:
def duration(func):
async def helper(func, *args, **kwargs):
if asyncio.iscoroutinefunction(func):
print(f"this function is a coroutine: {func.__name__}")
return await func(*args, **kwargs)
else:
print(f"not a coroutine: {func.__name__}")
return func(*args, **kwargs)
@functools.wraps(func)
async def wrapper(*args, **kwargs):
start_ts = time.time()
result = await helper(func, *args, **kwargs)
dur = time.time() - start_ts
print('{} took {:.2} seconds'.format(func.__name__, dur))
return result
return wrapper
或者,如果要保留contextmanager,也可以执行以下操作:
def duration(func):
""" decorator that can take either coroutine or normal function """
@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)
async def wrapper(*args, **kwargs):
if not asyncio.iscoroutinefunction(func):
with wrapping_logic():
return func(*args, **kwargs)
else:
with wrapping_logic():
return (await func(*args, **kwargs))
return wrapper
此答案与接受的答案之间的差异不大。主要是我们只需要创建一个异步包装器,然后在函数是协程的情况下等待该函数。
在我的测试中,此示例代码可在修饰后的函数的try/except
块以及with
语句中运行。
对于我来说,尚不清楚为什么异步FastAPI方法需要对包装进行异步。
答案 2 :(得分:0)
与 Anatoly 一致,此解决方案将以前的答案放在一起,并确保保留 func 的原始类型(如果同步保持装饰 func 同步,如果异步保持异步):
import time
import asyncio
from contextlib import contextmanager
import functools
def decorate_sync_async(decorating_context, func):
if asyncio.iscoroutinefunction(func):
async def decorated(*args, **kwargs):
with decorating_context():
return (await func(*args, **kwargs))
else:
def decorated(*args, **kwargs):
with decorating_context():
return func(*args, **kwargs)
return functools.wraps(func)(decorated)
@contextmanager
def wrapping_logic(func_name):
start_ts = time.time()
yield
dur = time.time() - start_ts
print('{} took {:.2} seconds'.format(func_name, dur))
def duration(func):
timing_context = lambda: wrapping_logic(func.__name__)
return decorate_sync_async( timing_context, func )
decorate_sync_async
现在可以与任何包装逻辑 (contextmanager) 重用,以创建同时适用于同步和异步功能的装饰器。
使用(并检查):
@duration
def sync_hello():
print('sync_hello')
@duration
async def async_hello():
await asyncio.sleep(0.1)
print('async_hello')
async def main():
print(f"is {sync_hello.__name__} async? "
f"{asyncio.iscoroutinefunction(sync_hello)}") # False
sync_hello()
print(f"is {async_hello.__name__} async? "
f"{asyncio.iscoroutinefunction(async_hello)}") # True
await async_hello()
if __name__ == '__main__':
asyncio.run(main())
输出:
sync_hello async? False
sync_hello
sync_hello took 0.0 seconds
is async_hello async? True
async_hello
async_hello took 0.1 seconds