|
| 1 | +.. _guide_compatible: |
| 2 | + |
| 3 | +================ |
| 4 | +Sync/Async Reuse |
| 5 | +================ |
| 6 | + |
| 7 | +The :py:mod:`asyncstdlib` only re-implements functions and classes |
| 8 | +that benefit from an async implementation. |
| 9 | +In some cases, a synchronous implementation is already |
| 10 | +sufficient to cover the async case as well. |
| 11 | + |
| 12 | +Example: async property |
| 13 | +======================= |
| 14 | + |
| 15 | +A prominent example is an "``async`` ``property``": |
| 16 | +a computed attribute that allows to run ``async`` code as well. |
| 17 | +This is useful for example to fetch data for the attribute |
| 18 | +from a remote database or server. |
| 19 | + |
| 20 | +As it turns out, we can directly use the builtin :py:class:`property` for this! |
| 21 | + |
| 22 | +.. code-block:: python3 |
| 23 | +
|
| 24 | + # python3 -m asyncio |
| 25 | + class Remote: |
| 26 | + _count = 0 |
| 27 | + @property # <== builtin @property ... |
| 28 | + async def attribute(self): # ... around an async method |
| 29 | + await asyncio.sleep(1) # let's pretend to do some work... |
| 30 | + self._count += 1 |
| 31 | + return "Na" * self._count |
| 32 | +
|
| 33 | + instance = Remote() |
| 34 | + print(await instance.attribute) # waits 1 second, prints Na |
| 35 | + print(await instance.attribute) # waits 1 second, prints NaNa |
| 36 | +
|
| 37 | +In principle, we could also define setters and deleters |
| 38 | +– however, Python has no syntax for async assignment or deletion |
| 39 | +which limits the advantage of using a :py:class:`property` in the first place. [1]_ |
| 40 | + |
| 41 | +Identifying reusability |
| 42 | +======================= |
| 43 | + |
| 44 | +In general, a utility is sync/async compatible when it takes a callable but does not |
| 45 | +depend on the concrete result. |
| 46 | +For example, a `property` getter just prepares some attribute value |
| 47 | +– which may as well be an awaitable. |
| 48 | +In contrast, the similar :py:func:`~asyncstdlib.functools.cached_property` must access |
| 49 | +the concrete result to store it – this requires async capabilities for the async case. |
| 50 | + |
| 51 | +Some examples for async compatible parts of the standard library include: |
| 52 | + |
| 53 | +* Factory descriptors such as :py:class:`property`, :py:class:`classmethod` and :py:class:`staticmethod` |
| 54 | +* Factories such as :py:func:`functools.partial` and :py:func:`functools.partialmethod` |
| 55 | +* Selectors such as :py:func:`functools.singledispatch` and :py:func:`functools.singledispatchmethod` |
| 56 | +* Modifiers such as :py:func:`functools.wraps` and :py:func:`functools.update_wrapper` |
| 57 | +* Special method operators not enforcing result types such as :py:func:`reversed` and :py:func:`~operator.__add__` |
| 58 | + |
| 59 | +Most of these merely wrap a callable to either modify it directly |
| 60 | +(such as :py:func:`functools.wraps`) |
| 61 | +or call it regardless of the return type |
| 62 | +(such as :py:func:`functools.partial`). |
| 63 | +Note that some functions such as :py:func:`~operator.__add__` *usually* work for the |
| 64 | +`async` case, but may fail in some subtle edge case – such as not being able to see |
| 65 | +a :py:data:`NotImplemented` return value. |
| 66 | + |
| 67 | +.. [1] Using `setattr` and `delattr` one can asynchronously run a setter/getter, |
| 68 | + for example `await setattr(instance, "attribute")`. However, with the lack |
| 69 | + of specific syntax this offers little to no advantage over using a method. |
0 commit comments