jim jarnac
jim jarnac

Reputation: 5152

Python, asyncio: decorator in class to simplify loop syntax

Lets consider the following example of a class containing an asyncio loop and an async coroutine:

import asyncio

class Async:
    def __init__(self):
        self.loop=asyncio.get_event_loop()

    async def function(self, word):
        print(word)
        await asyncio.sleep(1.0)

a=Async()
a.loop.run_until_complete(a.function("hello_world"))

This does work.
I would like to create a decorator so that i can simplify the syntax of the code calling function to

a.function("hello_world")

I tried the following:

class Async:
    def __init__(self):
        self.loop=asyncio.get_event_loop()

    def async_loop(f):
        def decorated(*args, **kwargs):
            self.loop.run_until_complete(f(*args, **kwargs))

    @async_loop
    async def function(self, word):
        print(word)
        await asyncio.sleep(1.0)

a=Async()
a.function("hello_world")

At that point i receive the error: 'NoneType' object is not callable. - I also tried to have the decorator function outside of the class, but i got the same error. I'm not sure whether the decorator function best stands inside the claass (as a method) or outside. I'm quite new to python so Asyncio, decorator, and decorators in classes are still quite confusing for me. Any good soul would have an idea how to do that code correctly?

Upvotes: 4

Views: 3156

Answers (1)

PaulMcG
PaulMcG

Reputation: 63709

Decorators inside classes are a mess because self has to creep in everywhere.

Here is a working version of your code:

import asyncio

class Async:
    def __init__(self):
        self.loop=asyncio.get_event_loop()

    def async_loop(f):
        def decorated(self, *args, **kwargs):
            self.loop.run_until_complete(f(self, *args, **kwargs))
        return decorated

    @async_loop
    async def function(self, word):
        print(word)
        await asyncio.sleep(1.0)

a=Async()
a.function("hello_world")

You can make it more "selfless" if you just declare the event loop inside async_loop, or even better, declare the decorator outside of the class:

def async_loop(f):
    loop = asyncio.get_event_loop()
    def decorated(*args, **kwargs):
        loop.run_until_complete(f(*args, **kwargs))
    return decorated

class Async:
    @async_loop
    async def function(self, word):
        print(word)
        await asyncio.sleep(1.0)

a=Async()
a.function("hello_world")

So now it starts to raise the question, "why is this in a class in the first place?" And another question, "isn't there a decorator out there that does this already?"

Upvotes: 6

Related Questions