web-dev-qa-db-fra.com

Utiliser une coroutine comme décorateur

dans ce scénario:

async def foo(f):
    async def wrapper(*args, **kwargs):
        return f(*args, **kwargs)
    return wrapper

@foo
async def boo(*args, **kwargs):
    pass

l'appel à foo comme décorateur pour boo décorateur est-il un appel asynchrone?

--Première édition: comment gérer une chaîne de coroutines en tant que décorateurs?

18
Juggernaut

Merci au commentaire de @ blacknght, considérant

def foo():
    def wrapper(func):
        @functools.wraps(func)
        async def wrapped(*args):
             # Some fancy foo stuff
            return await func(*args)
        return wrapped
    return wrapper

et

def boo():
    def wrapper(func):
        @functools.wraps(func)
        async def wrapped(*args):
            # Some fancy boo stuff
            return await func(*args)
        return wrapped
    return wrapper

comme deux décorateurs, et

@foo()
@boo()
async def work(*args):
    pass

Comme foo enveloppe la coroutine work, la clé est de await la func(*arg) dans les deux décorateurs.

26
Juggernaut
def foo(f):
    async def wrapper(*args, **kwargs):
        return await f(*args, **kwargs)
    return wrapper

@foo
async def boo(*args, **kwargs):
    pass

Votre décorateur doit être une fonction normale et cela fonctionnera bien.

Lorsqu'un décorateur est évalué python exécute la méthode avec la fonction comme argument.

@foo
async def boo():
    pass

Évalue à:

__main__.boo = foo(boo)

Si foo est un type de fonction asynchrone (main. Boo) sera un objet coroutine, pas un objet fonction. Mais si foo est une fonction de synchronisation régulière, elle sera évaluée immédiatement et main. Boo sera le wrapper retourné.

6
user11919393

Voici une autre approche utilisant la bibliothèque decorator (c'est-à-dire pip install decorator première):

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())

Production:

42
division by zero
1
Kevin Lyons