Sync/Async Reuse

The asyncstdlib only re-implements functions and classes that benefit from an async implementation. In some cases, a synchronous implementation is already sufficient to cover the async case as well.

Example: async property

A prominent example is an “async property”: a computed attribute that allows to run async code as well. This is useful for example to fetch data for the attribute from a remote database or server.

As it turns out, we can directly use the builtin property for this!

# python3 -m asyncio
class Remote:
    _count = 0
    @property                   # <== builtin @property ...
    async def attribute(self):  # ... around an async method
        await asyncio.sleep(1)  # let's pretend to do some work...
        self._count += 1
        return "Na" * self._count

instance = Remote()
print(await instance.attribute)  # waits 1 second, prints Na
print(await instance.attribute)  # waits 1 second, prints NaNa

In principle, we could also define setters and deleters – however, Python has no syntax for async assignment or deletion which limits the advantage of using a property in the first place. [1]

Identifying reusability

In general, a utility is sync/async compatible when it takes a callable but does not depend on the concrete result. For example, a property getter just prepares some attribute value – which may as well be an awaitable. In contrast, the similar cached_property() must access the concrete result to store it – this requires async capabilities for the async case.

Some examples for async compatible parts of the standard library include:

Most of these merely wrap a callable to either modify it directly (such as functools.wraps()) or call it regardless of the return type (such as functools.partial()). Note that some functions such as __add__() usually work for the async case, but may fail in some subtle edge case – such as not being able to see a NotImplemented return value.