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()
The await operator is used to wait for a Promise and get its fulfillment value. It can only be used inside an async function or at the top level of a module.
Handling errors in async functions is very easy. Promises have the . catch() method for handling rejected promises, and since async functions just return a promise, you can simply call the function, and append a . catch() method to the end.
async and await Inside an async function, you can use the await keyword before a call to a function that returns a promise. This makes the code wait at that point until the promise is settled, at which point the fulfilled value of the promise is treated as a return value, or the rejected value is thrown.
The current method calls an async method that returns a Task or a Task<TResult> and doesn't apply the Await operator to the result. The call to the async method starts an asynchronous task. However, because no Await operator is applied, the program continues without waiting for the task to complete.
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.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With