Using a coroutine as decorator
Thanks to @blacknght's comment, considering
def foo():
def wrapper(func):
@functools.wraps(func)
async def wrapped(*args):
# Some fancy foo stuff
return await func(*args)
return wrapped
return wrapper
and
def boo():
def wrapper(func):
@functools.wraps(func)
async def wrapped(*args):
# Some fancy boo stuff
return await func(*args)
return wrapped
return wrapper
as two decorators, and
@foo()
@boo()
async def work(*args):
pass
As the foo
is wrapping the work
coroutine, the key is to await
the func(*arg)
in both decorators.
Here is an alternate approach using the decorator
library (i.e. pip install decorator
first):
import asyncio
import decorator
@decorator.decorator
async def decorate_coro(coro, *args, **kwargs):
try:
res = await coro(*args, **kwargs)
except Exception as e:
print(e)
else:
print(res)
@decorate_coro
async def f():
return 42
@decorate_coro
async def g():
return 1 / 0
async def main():
return await asyncio.gather(f(), g())
if __name__ == '__main__':
asyncio.run(main())
Output:
42
division by zero
def foo(f):
async def wrapper(*args, **kwargs):
return await f(*args, **kwargs)
return wrapper
@foo
async def boo(*args, **kwargs):
pass
Your decorator needs to be a normal function and it will work fine.
When a decorator is evaluated python executes the method with the function as the argument.
@foo
async def boo():
pass
Evaluates to:
__main__.boo = foo(boo)
If foo is an async function type(main.boo) will be a coroutine object, not a function object. But if foo is a regular synch function it will evaluate right away and main.boo will be the wrapper returned.