Using coroutines in asynchronous programming
Coroutines have evolved to be the unit of concurrency in asynchronous programming in Python. Because they are executed in a single thread they are not constrained by the global interpreter lock (GIL), and task switching is more lightweight than the same operation executed on threads or processes. Coroutines are suitable for many modern use cases that involve non-blocking I/O operations over data streams or network connections.
Support for asynchronous coroutines has also evolved. In early versions of Python (up to CPython 3.4), the recommended way to develop asynchronous capabilities for coroutines was to use the @asyncio.coroutine decorator over coroutine functions. This changed with the inclusion of the asyncio module in CPython 3.4 and the addition of the async/await expressions in CPython 3.5.
Important note
Since the asyncio.coroutine decorator has been deprecated in CPython 3.8 and removed in 3.11, we are going...