'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 1:[1]
Another way to do this, for funsies:
class aobject(object):
"""Inheriting this class allows you to define an async __init__.
So you can create objects by doing something like `await MyClass(params)`
"""
async def __new__(cls, *a, **kw):
instance = super().__new__(cls)
await instance.__init__(*a, **kw)
return instance
async def __init__(self):
pass
#With non async super classes
class A:
def __init__(self):
self.a = 1
class B(A):
def __init__(self):
self.b = 2
super().__init__()
class C(B, aobject):
async def __init__(self):
super().__init__()
self.c=3
#With async super classes
class D(aobject):
async def __init__(self, a):
self.a = a
class E(D):
async def __init__(self):
self.b = 2
await super().__init__(1)
# Overriding __new__
class F(aobject):
async def __new__(cls):
print(cls)
return await super().__new__(cls)
async def __init__(self):
await asyncio.sleep(1)
self.f = 6
async def main():
e = await E()
print(e.b) # 2
print(e.a) # 1
c = await C()
print(c.a) # 1
print(c.b) # 2
print(c.c) # 3
f = await F() # Prints F class
print(f.f) # 6
import asyncio
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
Solution 2:[2]
I would recommend a separate factory method. It's safe and straightforward. However, if you insist on a async version of __init__(), here's an example:
def asyncinit(cls):
__new__ = cls.__new__
async def init(obj, *arg, **kwarg):
await obj.__init__(*arg, **kwarg)
return obj
def new(cls, *arg, **kwarg):
obj = __new__(cls, *arg, **kwarg)
coro = init(obj, *arg, **kwarg)
#coro.__init__ = lambda *_1, **_2: None
return coro
cls.__new__ = new
return cls
Usage:
@asyncinit
class Foo(object):
def __new__(cls):
'''Do nothing. Just for test purpose.'''
print(cls)
return super().__new__(cls)
async def __init__(self):
self.initialized = True
async def f():
print((await Foo()).initialized)
loop = asyncio.get_event_loop()
loop.run_until_complete(f())
Output:
<class '__main__.Foo'>
True
Explanation:
Your class construction must return a coroutine object instead of its own instance.
Solution 3:[3]
Better yet you can do something like this, which is very easy:
import asyncio
class Foo:
def __init__(self, settings):
self.settings = settings
async def async_init(self):
await create_pool(dsn)
def __await__(self):
return self.async_init().__await__()
loop = asyncio.get_event_loop()
foo = loop.run_until_complete(Foo(settings))
Basically what happens here is __init__() gets called first as usual. Then __await__() gets called which then awaits async_init().
Solution 4:[4]
[Almost] canonical answer by @ojii
@dataclass
class Foo:
settings: Settings
pool: Pool
@classmethod
async def create(cls, settings: Settings, dsn):
return cls(settings, await create_pool(dsn))
Solution 5:[5]
I would like to show a much easier way of initiating coroutine based method within the __init__ method.
import asyncio
class Foo(object):
def __init__(self, settings):
self.settings = settings
loop = asyncio.get_event_loop()
self.pool = loop.run_until_complete(create_pool(dsn))
foo = Foo(settings)
Important point to be noted is:
- This makes the async code work as sync(blocking)
- This is not the best way to run async code, but when it comes to only initiation via a sync method eg:
__init__it will be a good fit. - After initiation, you can run the async methods from the object with await. i.e
await foo.pool.get(value) - Do not try to initiate via an
awaitcall you will getRuntimeError: This event loop is already running
Solution 6:[6]
The AsyncObj class with __ainit__ "async-constructor":
class AsyncObj:
def __init__(self, *args, **kwargs):
"""
Standard constructor used for arguments pass
Do not override. Use __ainit__ instead
"""
self.__storedargs = args, kwargs
self.async_initialized = False
async def __ainit__(self, *args, **kwargs):
""" Async constructor, you should implement this """
async def __initobj(self):
""" Crutch used for __await__ after spawning """
assert not self.async_initialized
self.async_initialized = True
await self.__ainit__(*self.__storedargs[0], **self.__storedargs[1]) # pass the parameters to __ainit__ that passed to __init__
return self
def __await__(self):
return self.__initobj().__await__()
def __init_subclass__(cls, **kwargs):
assert asyncio.iscoroutinefunction(cls.__ainit__) # __ainit__ must be async
@property
def async_state(self):
if not self.async_initialized:
return "[initialization pending]"
return "[initialization done and successful]"
Here is example of "async class":
class MyAsyncObject(AsyncObj):
async def __ainit__(self, param1, param2=0):
print("hello!", param1, param2)
# go something async, e.g. go to db
Usage:
async def example():
my_obj = await MyAsyncObject("test", 123)
Solution 7:[7]
Vishnu shettigar's answer is so far the simplest, except that his async_init method doesn't return the object itself so foo isn't assigned a Foo instance. As for OP's purpose, the most elegant way to construct the class IMHO is
import asyncio
class Foo:
def __init__(self, settings):
self.settings = settings
def __await__(self):
self.pool = asyncio.create_task(create_pool(dsn))
yield from self.pool
self.pool = self.pool.result()
return self
To initialize the object, do the following
def main():
loop = asyncio.get_event_loop()
foo = loop.run_until_complete(Foo(settings))
Or
async def main():
foo = await Foo(settings)
Solution 8:[8]
we could convert the async call to sync call by running the async code manually through asyncio.run()
class Foo:
async def __ainit__(self, param):
self._member = await some_async_func(param)
def __init__(self, param):
asyncio.run(self.__ainit__(param))
Solution 9:[9]
Depending on your needs, you can also use AwaitLoader from:
https://pypi.org/project/async-property/
From the docs:
AwaitLoaderwill call awaitinstance.load(), if it exists, before loading properties.
Solution 10:[10]
This worked for me in Python 3.9
from aiobotocore.session import AioSession
import asyncio
class SomeClass():
def __init__(self):
asyncio.run(self.async_init())
print(self.s3)
async def async_init(self):
self.s3 = await AioSession().create_client('s3').__aenter__()
Solution 11:[11]
Everyone can try? https://pypi.org/project/asyncinit/
- pip install asyncinit
from asyncinit import asyncinit
@asyncinit
class MyClass:
async def __init__(self, param):
self.val = await self.deferredFn(param)
async def deferredFn(self, x):
# ...
return x + 2
obj = await MyClass(42)
assert obj.val == 44
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
