Just a quick overview of how a for loops works in python
x = [1,2,3,4,5]
for i in x:
print(i)
OUTPUT :
1
2
3
4
5
The above code mentioned is equivalent to the code given below
x = [1, 2, 3, 4, 5]
y = iter(x)
try:
while True:
print(next(y))
except StopIteration as e:
pass
OUTPUT :
1
2
3
4
5
Coroutines are basically functions whose execution can be paused/suspended at a particular point, and then we can resume the execution from that same point later where we left last time and we can resume it whenever we want.
yield
keyword.So here’s where things get interesting
next
function.So here’s a basic example
def func():
print('Function Starts')
yield
print('Function Ends')
try:
y = func()
print(type(y))
next(y) # First part of the function executed
next(y) # Second part of the function executed
except StopIteration as e:
pass
OUTPUT :
<class 'generator'>
Function Starts
Function Ends
So here from the output we notice some things.
StopIteration
exception is thrown and caught, same happens in this case when last part of the coroutine is executed.Now this pausing of function in between is very interesting and has opened up a few possibilities
When function is paused, WE DO NOTHING which is the case that we just saw.
Suppose a variable is being modified several times in a function and we want the value of that particular variable at a certain checkpoint,
then when we pause that function on that particular checkpoint, it returns the value of that variable.
Let’s see it with an example
def func():
x = 5
print('Function Part 1')
yield x
x += 7
print('Function part 2')
yield x
print('Function part 3')
try:
y = func()
z = next(y) # Function part 1 executed
print(z)
z = next(y) # Function part 2 executed
print(z)
z = next(y) # Function part 3 executed and StopIteration exception raised
print(z) # This print will not be executed
except StopIteration as e:
pass
OUTPUT :
Function Part 1
5
Function part 2
12
Function part 3
Here value of x is returned by yield
at different checkpoints as function execution has been paused
Whenever we are executing the last part of the function and there is no yield left in the function then after executing that last part
StopIteration
exception will be raised.
As it happens with for loop and iterators that when an iterator try to execute the next function but no more elements are left in the iterable, it also raises the
StopIteration
exception.
Suppose we want to send a value(which can be a constant or a variable) at a certain checkpoint i.e at a certain state of a function.
We can also do that using yield
keyword. When we want to send a value, we’ll use send
function instead of next
.
Let’s see with an example
def func():
print('Function part 1')
x = yield
print(x)
print('Function part 2')
a = yield
print(a)
print('Function part 3')
try:
y = func()
next(y) # Function part 1 executed, to reach the first yield we used next
y.send(6) # Function part 2 executed and value sent 6
y.send(12) # Function part 2 executed and value sent 12 and StopIteration raised
except StopIteration as e:
pass
OUTPUT :
Function part 1
6
Function part 2
12
Function part 3
The reason we used
next
first before using send is, we can only usedsend
when we are at checkpointyield
andyield
is on the right side of the expression. So to reach that firstyield
we have to use thenext
function.
Now here comes an interesting application of coroutines. Suppose we want to switch back and forth between two functions like we do in multithreading.
But in multithreading until an interrupt is encountered by the OS it will keep executing but in this case we can switch whenever we want.
Let’s see with an example
def func1():
print('Function 1 part 1')
yield
print('Function 1 part 2')
yield
print('Function 1 part 3')
yield
print('Function 1 part 4')
yield
print('Function 1 part 5')
def func2():
print('Function 2 part 1')
yield
print('Function 2 part 2')
yield
print('Function 2 part 3')
yield
print('Function 2 part 4')
yield
print('Function 2 part 5')
try:
a = func1()
b = func2()
next(a) # Will execute Function 1 part 1
next(b) # Will execute Function 2 part 1
next(a) # Will execute Function 1 part 2
next(a) # Will execute Function 1 part 3
next(b) # Will execute Function 2 part 2
next(b) # Will execute Function 2 part 3
next(b) # Will execute Function 2 part 4
next(a) # Will execute Function 1 part 4
next(a) # Will execute Function 1 part 5 and raise StopIteration exception
except StopIteration as e:
pass
OUTPUT :
Function 1 part 1
Function 2 part 1
Function 1 part 2
Function 1 part 3
Function 2 part 2
Function 2 part 3
Function 2 part 4
Function 1 part 4
Function 1 part 5
Here in the above example we can see that we can switch back and forth between coroutines whenever we want.
So if we write our own custom scheduler which handles the switching between multiple coroutines then we can achieve with single threading which we do with multithreading.
Coroutines has many applications such as concurrency and other programming patterns can also be implemented like Producer Consumer or Sender Receiver in network programming which i’ll be sharing with you in the upcoming articles.
Coroutines are also the building blocks of many frameworks such as asyncio, twisted, aiohttp. They can also be chained together to make pipelines and solve problems.