pythonpython-3.xpython-asyncio

How to set class attribute with await in __init__


How can I define a class with await in the constructor or class body?

For example what I want:

import asyncio

# some code


class Foo(object):

    async def __init__(self, settings):
        self.settings = settings
        self.pool = await create_pool(dsn)

foo = Foo(settings)
# it raises:
# TypeError: __init__() should return None, not 'coroutine'

or example with class body attribute:

class Foo(object):

    self.pool = await create_pool(dsn)  # Sure it raises syntax Error

    def __init__(self, settings):
        self.settings = settings

foo = Foo(settings)

My solution (But I would like to see a more elegant way)

class Foo(object):

    def __init__(self, settings):
        self.settings = settings

    async def init(self):
        self.pool = await create_pool(dsn)

foo = Foo(settings)
await foo.init()

Solution

  • Most magic methods aren't designed to work with async def/await - in general, you should only be using await inside the dedicated asynchronous magic methods - __aiter__, __anext__, __aenter__, and __aexit__. Using it inside other magic methods either won't work at all, as is the case with __init__ (unless you use some tricks described in other answers here), or will force you to always use whatever triggers the magic method call in an asynchronous context.

    Existing asyncio libraries tend to deal with this in one of two ways: First, I've seen the factory pattern used (asyncio-redis, for example):

    import asyncio
    
    dsn = "..."
    
    class Foo(object):
        @classmethod
        async def create(cls, settings):
            self = cls()
            self.settings = settings
            self.pool = await create_pool(dsn)
            return self
    
    async def main(settings):
        settings = "..."
        foo = await Foo.create(settings)
    

    Other libraries use a top-level coroutine function that creates the object, rather than a factory method:

    import asyncio
    
    dsn = "..."
    
    async def create_foo(settings):
        foo = Foo(settings)
        await foo._init()
        return foo
    
    class Foo(object):
        def __init__(self, settings):
            self.settings = settings
    
        async def _init(self):
            self.pool = await create_pool(dsn)
    
    async def main():
        settings = "..."
        foo = await create_foo(settings)
    

    The create_pool function from aiopg that you want to call in __init__ is actually using this exact pattern.

    This at least addresses the __init__ issue. I haven't seen class variables that make asynchronous calls in the wild that I can recall, so I don't know that any well-established patterns have emerged.