Call event_loop one time in the init function to get the return value?
If you spin the event loop during __init__
, you won't be able to instantiate C
while the event loop is running; asyncio event loops don't nest.
[EDIT: After the second update to the question, it appears that the event loop gets run by a non-static method C.run
, so run_until_complete
in __init__
will work with the code as written. But that design is limited - for example it doesn't allow constructing another instance of C
or of a class like C
while the event loop is running.]
Make the __init__
function async? and run it in the event loop?
__init__
cannot be made async without resorting to very ugly hacks. Python's __init__
operates by side effect and must return None
, whereas an async def
function returns a coroutine object.
To make this work, you have several options:
Async C
factory
Create an async function that returns C
instances, such as:
async def make_c():
c = C()
await c._async_init()
return c
Such a function can be async without problems, and can await as needed. If you prefer static methods to functions, or if you feel uncomfortable accessing private methods from a function not defined in the class, you can replace make_c()
with a C.create()
.
Async C.r
field
You can make the r
property asynchronous, simply by storing a Future
inside of it:
class C:
def __init__(self):
self.a = 1
self.b = 2
loop = asyncio.get_event_loop()
# note: no `await` here: `r` holds an asyncio Task which will
# be awaited (and its value accessed when ready) as `await c.r`
self.r = loop.create_task(aioredis.create_redis('redis://localhost:6379'))
This will require every use of c.r
to be spelled as await c.r
. Whether that is acceptable (or even beneficial) will depend on where and how often it is used elsewhere in the program.
Async C
constructor
Although __init__
cannot be made async, this limitation doesn't apply to its low-level cousin __new__
. T.__new__
may return any object, including one that is not even an instance of T
, a fact we can use to allow it to return a coroutine object:
class C:
async def __new__(cls):
self = super().__new__(cls)
self.a = 1
self.b = 2
self.r = await aioredis.create_redis('redis://localhost:6379')
return self
# usage from another coroutine
async def main():
c = await C()
# usage from outside the event loop
c = loop.run_until_complete(C())
This last approach is something I wouldn't recommend for production code, unless you have a really good reason to use it.
- It is an abuse of the constructor mechanism, since it defines a
C.__new__
constructor that doesn't bother to return a C
instance;
- Python will notice the above and will refuse to invoke
C.__init__
even if you define or inherit its (sync) implementation;
- Using
await C()
looks very non-idiomatic, even (or especially) to someone used to asyncio.