30 Days Of Python πŸ‘¨β€πŸ’» - Day 15 - Generators

This article is a part of a 30 day Python challenge series. You can find the links to all the previous posts of this series here:
Today I explored all about the concept of generators in Python. The concept of generators exists in the JavaScript world as well. It was introduced in the ES6 version of JavaScript however I haven’t used them in practical JavaScript projects much. While reading about them today, I realized they can be of great use and are used in several Python libraries and frameworks.
 
So what are generators?
 
A generator is a special kind of function that returns an iterable set of values, one at a time, which means it can be looped over to get the values one by one. It is also sometimes referred to as functions that can be ‘paused’. In theory, generators sound quite complicated and confusing. It is best to explain using code examples. We hare already used a built-in Python generator range to generate a range of values.
  1. range_of_numbers = range(100# a generator  
  2. for num in range_of_numbers:   
  3.     print(num)  
  4. # Since range is a generator, it can be iterated or looped  
  1. def my_infinite_generator():  
  2.   num = 0  
  3.   while True:  
  4.     yield num  
  5.     num +=1  
  6.   
  7. result = my_infinite_generator()  
  8. for i in result:  
  9.   print(i) # Keeps on printing values infinitely!  
So what exactly is this generator function and how is it different from a normal function?
 
Functions can return only one value using the return statement. Once a function reaches a return statement, it returns the value and exits out. Whereas generator functions can return any number of values using a special keyword yield. Whenever the yield statement is reached, the function execution is paused and the control is passed to the whoever is calling the function.
 
The values of the generator can be extracted either by iteration or by using another built-in function called next on the generator. In the above block of code, the values of the generator are printed using iteration (for loop). The same can be done manually by using the next function.
  1. def my_infinite_generator():  
  2.   num = 0  
  3.   while True:  
  4.     yield num  
  5.     num +=1  
  6.   
  7. result = my_infinite_generator()  
  8. print(next(result)) # 0  
  9. print(next(result)) # 1  
  10. print(next(result)) # 2  
  11. print(next(result)) # 3  
  12. print(next(result)) # 4  
Note how the generator function is able to remember the value of num and is able to increment it. Whenever yield statement is reached, it saves the local variables and their state and then the control is transferred to the caller.
  1. def my_generator(max):  
  2.   num = 0  
  3.   while num < 3:  
  4.     yield num  
  5.     num +=1  
  6.   
  7. result = my_generator(3)  
  8. print(next(result)) # 0  
  9. print(next(result)) # 1  
  10. print(next(result)) # 2  
  11. print(next(result)) # Stop Iteration Error  
When an iteration is completed (in the above case once num = 3), the generator function automatically raises a StopIteration exception on further next calls to it. If the same generator is iterated using a for loop, the loop will automatically stop once the StopIteration is raised by the generator. The for loop internally handles this and terminates.
 

Performance benefits of using generators

 
Generator functions are memory efficient. Generators really come in handy when dealing with large sets of data that requires lot of processing to compute results. Memory is a finite resource and our systems can only hold a limited amount of data in memory.
 
For instance, if we have to create a function that accepts a number and prints the Fibonacci sequence till that number, the conventional approach would be something like this
  1. def fibonacci(num):  
  2.   sequence = []  
  3.   a,b = 0,1  
  4.   for item in range(num):  
  5.     sequence.append(a)  
  6.     temp = a  
  7.     a = b  
  8.     b = temp + b  
  9.   return sequence  
  10.   
  11. result = fibonacci(20)  
  12. print(result) # prints the fibonacci sequence  
In the above function, the entire sequence is stored in memory as a list. The result is only printed once the entire sequence is stored in memory. This is usually fine when the number is small. However, if the number is large, the memory usage increases drastically and the process may even be killed if the memory overflows.
 
Using a generator the above function can be made more memory efficient
  1. def fibonacci_generator(num):  
  2.   a,b = 0,1  
  3.   for item in range(num):  
  4.     yield a  
  5.     temp = a  
  6.     a = b  
  7.     b = temp + b  
  8.   
  9. for num in fibonacci_generator(20):  
  10.   print(num)  
In this case, only one value is stored in memory at a time, thus it is able to print values even if the provided number is very large.
 

Composition

 
Generators can be composed together or in other words, they can be piped together to combine results from different generators.
 
For instance, if we have to create a Fibonacci sequence with each value squared, they can be composed using two generator functions.
  1. def fibonacci_generator(num):  
  2.   a,b = 0,1  
  3.   for item in range(num):  
  4.     yield a  
  5.     temp = a  
  6.     a = b  
  7.     b = temp + b  
  8.   
  9. def square(nums):  
  10.   for num in nums:  
  11.     yield num**2  
  12.   
  13. result = square(fibonacci_generator(5))  
  14.   
  15. for num in result:  
  16.   print(num) # 0 1 1 4 9  
This kind of composition can be very useful while computing large data sets and applying different operations on them in layers.
 
That’s all about generators in Python in brief. Exploring generators today helped me solidify the mental model around the concept and I can now probably think of ways to implement them in JavaScript as well.
 
Tomorrow marks the start of the third week of this current Python challenge and as per my planned roadmap, I will be exploring another essential topic in Python - Modules. I am sure that will be quite interesting.
 
Have a great one!


Similar Articles