I want to ask how about the possibilty of having async setters and getters combined with sync setter and getter (django-style). The idea is something as follows:
class MyClass:
def __init__(self, data):
self._data = data
@property
def data(self):
return self._data
@data.setter
def data(self, value):
# some sync stuff
self._data = data
@async_property
async def adata(self):
return self._data
@data.async_setter
async def adata(self, value):
# some async stuff
self._data = data
obj = MyClass("value")
print(await obj.adata) # "value"
obj.adata = "new-value"
print(await obj.adata) # "new-value"
This is an example, but I guess you can get the idea. Do you guys have any idea on how can i achieve something similar. I’m not really sure about the obj.adata = “new-value” because i don’t know where if need to put the await.
Because property doesn’t care what the return type of the getter is, you can just put it on an async function:
class MyClass:
@property
async def data(self):
return self._get_data()
c = MyClass()
result = await c.data
Basically, putting async on a function lets you use await within it, but it’s still a normal function that returns an awaitable object. These objects can be passed around as normal until you await it, which is why property just passes it through.
(Side note, I think this is a fairly unintuitive API design and I would never recommend it. But it’s a nice little illustration of the fact that awaitable results are not intimately connected to the definition of the function.)
And as you point out, there’s not really anywhere to put an await on an assignment, which means you can’t “pump” the messages to actually execute the coroutine. Syntactically, the only option here is a regular function.
Thanks for the great insights! I never thought about @properties as a function which could pass another function but it makes total sense. So in case I want to have the async setter and getter you recommend something as follows?
class MyClass:
def __init__(self, data):
self._data = data
@property
def data(self):
return self._data
@data.setter
def data(self, value):
# some sync stuff
self._data = data
async def aset_data(self):
return self._data
async def aget_data(self, value):
# some async stuff
self._data = data
obj = MyClass("value")
print(await obj.aget_data())
await obj.aset_data("new-value")
print(await obj.aget_data()) # "new-value"
At least to me, now that you mention it, it feels much cleaner but more verbose and less “magic” in the sense of I cannot hide it as before. Is this a correct syntax or do you recommend something else?
I usually recommend not making setters do anything “interesting”, which means they shouldn’t ever need to be async (which is “something interesting” by my definition). I don’t even like it when people add a setter to do validation (who expects x = y to raise?).
So yes, I think if you need async, then just use a function and give it a meaningful name.
But I’m not consensus in this area by far, so I’m sure you can find someone to give you a different opinion if you keep asking