Consider the problem of maintaining a running sum of integers, which we will refer to as the Summer problem (a nod to the season). We will demonstrate how co-routines work by trying to implement running sum.
🔗Traditional Class-Based Solution
The simplest approach uses a class with explicit add and sum methods to maintain a running total:
class Summer:
def(self, total=0):
self.total = total
def add(self, m=0):
self.total += m
def sum(self):
return self.total
summer =()
summer.(10)
assert summer.() == 10🔗Callable-Objects Based Solution
To reduce object complexity, we can implement __call__ method that both updates the sum and returns the current value.
class SummerV0:
def(self, total=0):
self.total = total
def(self, m=0):
self.total += m
return self.total
summer =()
assert(10) == 10
assert() == 10🔗Co-Routines with Global State Based Solution
A coroutine implementation can be written using global state for maintaining the running sum:
def summer_v1(total_init=0):
global total
total = total_init
while True:
z = yield
total += z if z else 0
summer =()
next()
summer.(10)
assert total == 10🔗Stateful Co-Routines Based Solution
A coroutine implementation can store state in local variable as well:
def summer_v2(total=0):
total = total
while True:
z = yield total
total += z if z else 0
summer =()
summer_again =(12)
assert next() == 0
assert next() == 12
assert summer.(10) == 10
assert summer_again.(10) == 22🔗Automated Initialization Pattern
Co-routines require explicit initialization with .send(None) or next. Otherwise it raise Exception TypeError: can't send non-None value to a just-started coroutine. This can be avoided using decorators as follows:
def safety_wrapper(cr):
"""the safety wrapper"""
def init_cr(*args, **kwargs):
dec_cr =(*, **)
dec_cr.(None)
return dec_cr
return init_cr
@safety_wrapper
def summer_v3(total_init=0):
total = total_init
while True:
z = yield total
total += z if z else 0
summer =()
assert summer.(10) == 10🔗Advance Co-routine Handling
Coroutine has advanced feature other than send like:
close: Terminates the coroutine execution, causing subsequent
sendoperations to raiseStopIterationexceptions. This ensures the coroutine enters a finalized state. Specialized action like clean-up can be take by handlingGeneration ExitException.throw: Enables the propagation of exceptions within the coroutine context. This feature can be leverage to specialized action.
class Reset(Exception):
pass
@safety_wrapper
def summer_v4(total_init=0):
total = total_init
while True:
try:
z = yield total
total += z if z else 0
except Reset:
total = 0
except GeneratorExit:
print(f"total={}")
return
summer =()
assert summer.(10) == 10
summer.() # prints `total=10`
exception = False
try:
summer.(11)
except StopIteration:
exception = True
assert exception
summer_1 =()
assert summer_1.(10) == 10
assert summer_1.(10) == 20
assert summer_1.() == 0
assert summer_1.(10) == 10