For this post in my Python syntactic sugar series, I am going to cover
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
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.
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.
await expression evolved from
yield from, but with two key tweaks: checking that the object is awaitable, and also supporting awaitable objects which define
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
Thanks to how Python built up to
await we already had the building blocks to unravel the syntax and essentially devolve to earlier versions of Python!