Reputation: 5424
I'm pretty new to the asyncio for python 3.6
So the thing is I have a class and I want to init some property in there. And one of the property is the return value from an async function.
What's the best practice to do this?
Call event_loop one time in the init function to get the return value?
Make the __init__ function async? and run it in the event loop?
Cheers!
UPDATE AGAIN:Following is my code:
import asyncio
import aioredis
from datetime import datetime
class C:
def __init__(self):
self.a = 1
self.b = 2
self.r = None
asyncio.get_event_loop().run_until_complete(self._async_init())
async def _async_init(self):
# this is the property I want, which returns from an async function
self.r = await aioredis.create_redis('redis://localhost:6379')
async def heart_beat(self):
while True:
await self.r.publish('test_channel', datetime.now().__str__())
await asyncio.sleep(10)
def run(self):
asyncio.get_event_loop().run_until_complete(self.heart_beat())
c=C()
c.run()
Upvotes: 13
Views: 13176
Reputation: 154916
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:
C
factoryCreate 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()
.
C.r
fieldYou 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.
C
constructorAlthough __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.
C.__new__
constructor that doesn't bother to return a C
instance;C.__init__
even if you define or inherit its (sync) implementation;await C()
looks very non-idiomatic, even (or especially) to someone used to asyncio.Upvotes: 10