I want to write a lazyloadable property. It could load data when first __get__(), But __get__() couldn't set as async. Is there any way to await obj.load() finished then return the getter's return.
class LazyLoadableProperty:
def __get__(self, obj, owner_class):
if obj is None:
return self
if not self.__loaded:
# task = asyncio.create_task(obj.load())
# while True:
# break when task is finished
self.__loaded = True
return self.__get(obj) # __get() is a costume getter set when init.
...
class A(LazyLoadable):
_name: str
def __init__():
self._name = ""
async def load(): # LazyLoadable is a abc and LazyLoadable.load is an async func.
# load from http
asyncio.sleep(1)
self._name = "Jax"
@LazyLoadableProperty
def name(self):
return self._name
async def main():
a = A()
print(a.name)
asyncio.run(main())
- If I directly call: RuntimeWarning: coroutine 'A.load' was never awaited
obj.load()
- If use run_until_complete: RuntimeError: This event loop is already running
asyncio.get_event_loop().run_until_complete(obj.load())
Some other ways may modified too much existing code I don't want to use:
__get__()returns an Awaitable and await whenawait a.name.- LazyLoadable is a abc. It's hard to change all
<subclass>.load()to sync func.
You're missing an important concept here. When your script encounters this line:
the value of
a.nameis not immediately available. That's the whole point of what you're trying to do. Consider: how is the value going to become available whilemainwaits for it? In your comment you say it's from doing an HTTP call. But to do an HTTP call, you must execute code. And in a single-threaded asyncio program, the only place where such code can execute is in another Task, outside of themainfunction. And to yield control to another Task, you need to execute an await expression. In the code you present there is no await statement at all, so your approach has no chance of working.Your main function must look something like this:
The await expression pauses
mainuntil another Task computes the value ofa.name. This may not be what you wanted, but if the value ofa.nameis not available yet, there is really no other logical possibility.As you can see from this little code snippet, the expression
a.namewill be an awaitable object. It can also be a property - there is no rule against that. The following code, when you run it, delays for one second and then prints "Jax."This script does exactly the same thing.
The second version contains fewer keystrokes. But I personally prefer the first version, which makes it more clear that the property
nameis an awaitable object.There is no need to create a special class or invent a special type of property.
I also fixed your omission of the
selfargument in a couple of places and added the necesaryawaitin front ofasyncio.sleep.