How Do Python Coroutines Work?
Classical computer science focuses on CPU-bound problems. It is concerned with algorithms that complete computations quickly. But many networked programs spend their time not computing, but waiting for responses from remote machines. These I/O-bound programs present a very different challenge: wait for a huge number of network responses efficiently. The modern answer to this problem is asynchronous I/O, or “async”.
In the early days of async frameworks, programmers put their logic in callback functions that waited for network responses, made network requests, and then scheduled more callbacks to handle the next round of responses. Complex code descended into an unmanageable mess of spaghetti callbacks. Such code defeated traditional exception handling, and it was intimidating to debug.
Coroutines are a newly popular programming style. They are memory-efficient in I/O-bound applications, but they possess all the grace and simplicity callbacks lack. Best of all, we can keep using time-tested techniques for handling exceptions.
To begin to understand how coroutines work, we look at an example of coroutine code in action. It is a Python generator, but what does that mean? We explore the Python interpreter’s generator implementation, how it uses a stack frame and instruction pointer in an unorthodox way to pause and resume the generator at will.
Coroutines build upon generators: they can pause waiting for a network operation, and resume when the operation completes. I show a minimal implementation of the Future and Task classes used in asyncio to schedule coroutines.