Coroutines vs Coroutine Functions in Python
The subtle difference between coroutine and coroutine functions.

Introduction:
There is a common misconception that coroutine
functions and coroutine objects are the same. Well! Not exactly. Though they co-exist, there is a teeny tiny difference between both. It is important to understand the difference between them.
What are coroutines?
Simply put, A coroutine
is a regular Python function that is prefixed with the keyword async
. To get a quick introduction of asyncio
library in Python, please refer here. Coroutines created with async def
are called native coroutines as they are built into the standard library from python
3.5.
Coroutines functions vs Coroutines:
To understand the difference, let’s write a simple asynchronous function using the asyncio
library.
import asyncio, time
async def hello_world():
print(time.ctime())
await asyncio.sleep(1.0)
print(time.ctime())
loop = asyncio.get_event_loop()
task = loop.create_task(hello_world())
loop.run_until_complete(task)
loop.close()
The moment we look at this function we say that this is a coroutine. However, this assumption is only partially right. Please note that asynchronous functions are NOT coroutines. They are just asynchronous functions
like any other Python function.Period.
Don’t believe it yet? Let’s test it out. We shall try printing the type
of hello_world function
.
print(type(hello_world)) Output:
<class 'function'>
I was as surprised as you are. All these days, I was assuming the moment we declare a Python function with async def
they become coroutines. Apparently not. If they are not coroutines, then what are they? They are coroutine functions.
Let’s quickly test this using the inspect
module in python
.
import inspect print(inspect.iscoroutinefunction(hello_world)) Output:
True
Well, then what is a coroutine?
A coroutine
is created when a coroutine function is executed/evaluated. In other words, when an async def
function is called, it will return a coroutine.
import asyncio, time
async def hello_world():
print(time.ctime())
await asyncio.sleep(1.0)
print(time.ctime())
coro = hello_world()
print(type(coro))
print(inspect.iscoroutine(coro))
print(inspect.iscoroutinefunction(coro))
Output:
<class 'coroutine'>
True
False
Voila! We have printed the type of coro
variable which has the evaluated function hello_world()
and the type is coroutine
. The inspect
module returns true for iscoroutine()
and returns False
for iscoroutinefunction()
.
This is exactly how generators
work as well. Just like coroutines and coroutine functions we have generators and generator functions.
def get_number():
values = range(100)
for i in values:
if i % 2 == 0:
yield i
nums_ = get_number()
print(type(get_number))
print(type(nums_))
print(inspect.isgeneratorfunction(get_number))
print(inspect.isgenerator(nums_))
Output:
<class 'function'>
<class 'generator'>
True
True
Summary:
- Coroutines and coroutine functions are not the same.
- A coroutine is what we get by evaluating a coroutine function
- A function prefixed with
async def
is a coroutine function - A function with a
yield
keyword is agenerator
function - A generator function evaluates to a generator.
Originally published at https://dock2learn.com on February 10, 2022.
More content at plainenglish.io. Sign up for our free weekly newsletter. Get exclusive access to writing opportunities and advice in our community Discord.