Coroutines are special functions that differ from usual ones in four aspects:
- exposes several entry points to a function. An entry point is the line of code inside the function where it will take control over the execution.
- can receive a different input in every entry point while executing the coroutine.
- can return different outputs as response to the different entry points.
- can save control state between entry points calls.
Python implements coroutines starting in Python 2.5 by reusing the generator syntax, as defined in PEP 342 - Coroutines via Enhanced Generators.
Generator syntax is defined in PEP 255 - Simple generators. I covered briefly the generator functionality in a previous post. The basic usage of generators is creating an iterator over a data source. For example, the function in the snippet below returns a generator that iterates from a specific number down to 0, decreasing an unit in every iteration.
In the example above, the keyword yield is used to return a new value in every iteration while consuming the generator. It’s interesting to note that a generator can be consumed only once, opposite to a list that can be consumed/iterate as much as needed. A generator is considered exhausted upon being consumed the first time.
PEP 342 takes advantage of the keyword yield for pointing out entry points where the function/coroutine will receive inputs while being executed. Let’s see a very simple example of a coroutine that concatenates every string inserted by the user from the command line:
What is really interesting is how the coroutine execution is suspended and resumed by means of the yield keyword, allowing the program flow to be moved from the coroutine to the external program and back to it.
As a side project I have implemented a tiny library called washington that exposes a chainable API for building a coroutines stream. I had a lot of fun while digging into the implementation, even though the real usage of the library is expected to be very limited