Concepts - Coroutine in Python

Published on
4 mins read
––– views
Photo by Chris Ried on Unsplash
python-notes

A Coroutine is generator which is capable of constantly receiving input data, process input data and may or may not return any output.

Coroutines are majorly used to build better Data Processing Pipelines.

Similar to a generator, execution of a coroutine stops when it reaches yield statement.

A Coroutine uses send method to send any input value, which is captured by yield expression.

Syntax - Usage

# Syntax
# Declare
def coroutine_function():
while True:
data = yield
# any data operations
print(f'Name: {data} \nLength: {len(data)}')
# Initiate
c = coroutine_function()
next(c)
# Usage
c.send('Vijay Anand')
c.close()

Output

Name: Vijay Anand
Length: 11

Coroutine - Example...

As seen in Example1 and Example2, passing input to coroutine is possible only after the first next function call.

Example - 1

# coroutine -> 1
def string_parser(func):
while True:
name = yield
for x in name.split():
func.send(x)
# coroutine -> 2
def string_length():
while True:
string = yield
print("Length of '{}' : {}".format(string, len(string)))
f = string_length()
next(f)
s = string_parser(f)
next(s)
s.send('Jack Black')

Output

Length of 'Jack' : 4
Length of 'Black' : 5

Many programmers may forget to do so, which results in error.

Such a scenario can be avoided using a decorator as shown in Example 3.

Version - 1

def TokenIssuer():
tokenId = 0
while True:
name = yield
tokenId += 1
print('Token number of', name, ':', tokenId)
t = TokenIssuer()
next(t)
t.send('George')
t.send('Rosy')
t.send('Smith')

Output

Token number of George : 1
Token number of Rosy : 2
Token number of Smith : 3

Version - 2

def TokenIssuer(tokenId=0):
try:
while True:
name = yield
tokenId += 1
print('Token number of', name, ':', tokenId)
except GeneratorExit:
print('Last issued Token is :', tokenId)
t = TokenIssuer(100)
next(t)
t.send('George')
t.send('Rosy')
t.send('Smith')
t.close()

Output

Token number of George : 101
Token number of Rosy : 102
Token number of Smith : 103

Version - 3

def coroutine_decorator(func):
def wrapper(*args, **kwdargs):
c = func(*args, **kwdargs)
next(c)
return c
return wrapper
@coroutine_decorator
def TokenIssuer(tokenId=0):
try:
while True:
name = yield
tokenId += 1
print('Token number of', name, ':', tokenId)
except GeneratorExit:
print('Last issued Token is :', tokenId)
t = TokenIssuer(100)
t.send('George')
t.send('Rosy')
t.send('Smith')
t.close()

output

Token number of George : 101
Token number of Rosy : 102
Token number of Smith : 103
Last issued Token is : 103

Double yield in a single coroutine

def nameFeeder():
while True:
fname = yield
print('First Name:', fname)
lname = yield
print('Last Name:', lname)
n = nameFeeder()
next(n)
n.send('George')
n.send('Williams')
n.send('John')
n.close()

output

First Name: George
Last Name: Williams
First Name: John

error scenario without starting generator next()

def stringDisplay():
while True:
s = yield
print(s * 3)
c = stringDisplay()
c.send('Hi!!') # TypeError: can't send non-None value to a just-started

Output

Traceback (most recent call last):
File "C:\Users\hai\AppData\Roaming\JetBrains\PyCharmCE2021.2\scratches\coroutine.py", line 138, in <module>
c.send('Hi!!') # TypeError: can't send non-None value to a just-started
TypeError: can't send non-None value to a just-started generator

Best Example for Coroutine Program in Python

#!/bin/python3
import sys
# Define the function 'coroutine_decorator' below
def coroutine_decorator(coroutine_func):
def wrapper(*args, **kwdargs):
c = coroutine_func(*args, **kwdargs)
next(c)
return c
return wrapper
# Define the coroutine function 'linear_equation' below
@coroutine_decorator
def linear_equation(a, b):
while True:
x = yield
print("Expression, {}*x^2 + {}, with x being {} equals {:.2f}".format(a, b, x, a * x ** 2 + b))
# Define the coroutine function 'numberParser' below
@coroutine_decorator
def numberParser():
while True:
x = yield
equation1 = linear_equation(3, 4)
equation1.send(x)
equation2 = linear_equation(2, -1)
equation2.send(x)
# code to send the input number to both the linear equations
def main(x):
n = numberParser()
n.send(x)
if __name__ == "__main__":
x = float(input("Enter the x value in float: "))
res = main(x)

Output

Enter the x value in float: 2.4
Expression, 3*x^2 + 4, with x being 2.4 equals 21.28
Expression, 2*x^2 + -1, with x being 2.4 equals 10.52