Async setter and getter

Hello,

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.

4 Likes

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 :wink:

4 Likes