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's because I covered it in my other post.
Unravelling async
Let's 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's 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
.
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!