Monday, May 24, 2021

Brett Cannon: Unravelling `async` and `await`

For this post in my Python syntactic sugar series, I am going to cover async and await.

Now when I started to think about this post I was worried it was going to be rather long and arduous to research (although I believe class is going to ultimately win that crown 😜), but then I remembered I had written a blog post about how async and await worked in Python 3.5. As part of that post I went through the history of how Python got to that point of asynchronous programming, which meant I had already dived into the details of how Python evolved earlier syntax to what it has today! Thus this post is going to draw heavily from my async history post to save myself some time, so if you feel I skimmed over details then chances are it&aposs because I covered it in my other post.

Unravelling async

Let&aposs go from the outside in, tackling async first. It turns out that unravelling async def is very straightforward thanks to the types.coroutine decorator which is what async def evolved from. This decorator sets a flag on the code object for the generator to distinguish it from any other plain generator. Otherwise that&aposs it as async functions are fundamentally generators.

Unravelling await

The await expression evolved from yield from, but with two key tweaks: checking that the object is awaitable, and also supporting awaitable objects which define __await__().

The standard library provides the inspect.isawaitable() function to determine whether an object is awaitable. One tweak down.

The other tweak is how to call the coroutine via yield from. Awaitables can either define __await__() which returns an iterable or be a generator marked as a coroutine. As such, we need to support both situations. As with previous instances of special methods, we need to get the __await__() method from the type directly and then call it with the object. Otherwise both types of awaitables end up with an object that can be passed to yield from.

def _await(coroutine):
    """Simulate `await coroutine`."""
    if not inspect.isawaitable(coroutine):
        msg = f"object {builtins.type(coroutine)} can&apost be used in &aposawait&apos expression"
        raise TypeError(msg)
    coroutine_type = builtins.type(coroutine)
    try:
        __await__ = _mro_getattr(coroutine_type, "__await__")
    except AttributeError:
        awaitable = coroutine
    else:
        awaitable = __await__(coroutine)
    yield from awaitable
Code to simulate await

Thanks to how Python built up to async and await we already had the building blocks to unravel the syntax and essentially devolve to earlier versions of Python!



from Planet Python
via read more

No comments:

Post a Comment

TestDriven.io: Working with Static and Media Files in Django

This article looks at how to work with static and media files in a Django project, locally and in production. from Planet Python via read...