Python Generators

    In this tutorial, we will discuss what are generators in Python and how can we create a generator. We will also discuss how it is different from iterators and normal functions.

    What are the Generators?

    Though we can make our own Iterators using a class, __iter__() and __next__() methods, this could be tedious and complex. The generator is a technique used to create user-defined iterators. Basically, generators are the user-defined functions that are used to create user-defined Iterators.

    How to create a generator?

    As we have mentioned above generators are user-defined functions. Like a user-defined function, we can create a generator using def keyword, but in generators, we use a yield statement instead of a return. If a user-defined function contains a yield statement then it would be considered as a generator. The difference between the return and yield statement is a return statement returns a value from a function and terminates the function whereas the yield returns a value from a function but does not terminate, it pauses the function till the next call.

    Generator syntax

    def function_name():
        yield value

    Example:

    def gen():
        num=1
       print('This function call has called the first yield statement')
       yield num
    
        num_2=2
       print('We have used next() function to call the 2nd yield statement')
       yield num_2
    
        num_3 = 3
       print("This next() call has called the third yield statement")
        yield num_3
    
    a = gen()
    
    # First we will print a and look what is the output
    print("-----a has become an iterator----")
    print(a)
    
    print("----When we print next(a)----------")
    print(next(a))
    
    print("------When we again print next(a)---------")
    print(next(a))
    
    print('---Printing next(a) third time-------')
    print(next(a))
    
    print('---Printing next(a) forth time and this time it will throw an StopIteration error---------')
    print(next((a)))

    Output

    ----- a has become an iterator----
    <generator object gen at 0x02F0F070>
    ----When we print next(a)----------
    This function call has called the first yield statement
    1
    ------When we again print next(a)---------
    We have used next() function to call the 2nd yield statement
    2
    ---Printing next(a) third time-------
    This next() call has called the third yield statement
    3
    ---Printing next(a) forth time and this time it will throw a StopIteration error---------
    Traceback (most recent call last):
    StopIteration

    Behind the code

    In the above code, we create function gen() which has three yield statements which means the generator is able to return 3 values.  When we assign the function gen() to a variable a the variable became an iterator object.

    Difference between a normal user-defined function and generator

    Normal Function Generator
    A normal function can contain only return statement A generator should have a yield statement it could also have a return statement
    When we call a function, it starts executing immediately With its yield statement, it returns an Iterator object but it does not start execution immediately
    We can not apply next() function on its returned object We can perform next() function on its returned object
    Once the value is returned from the function the function gets terminated and give control back to the caller When the yield returns an object, it pauses the function and gives control back to the caller
    Every time the function gets call its local variable reset. The local variables of the generator function remain in their state until the next call from the caller.

    Python Generator with loops

    In the above example, we have used 3 different yield statements to return 3 different values instead of writing a yield statements, again and again, we use loops that can serve the same purpose. We often use loops for the yield statements, this makes our code less complex, and it secures our code from StopIteration error.

    Example:

    def gen():
        for num in range(1,4):
            yield num
    
    a = gen()
    for c in a:
       print(c)

    Output:

    1
    2
    3

    Behind the code

    In this example we have used for loop to iterate upon a, here a is an iterator.

    Python Generator Expression

    Like a lambda anonymous function, we can create anonyms generators, it is also known as generator expression. The generator expression looks like list comprehension the only difference instead of using sq. brackets we use parenthesis. We use a generator instead of list comprehension because it is more memory efficient because it does not store any value.

    Example:

    list_comprehension = [i*2 for i in range(4)]
    print("The type of list_comprehension is:",type(list_comprehension))
    
    generator = (i*2 for i in range(4))
    print("The type of generator is:",type(generator))

    Output:

    The type of list_comprehension is: <class 'list'>
    The type of generator is: <class 'generator'>

    Behind the code

    In this example, we have created two objects one is a list and another is a generator. Variable list_comprehension is a list build using python list comprehension and another variable generator is a generator build using generator expression.

    Why do we use a generator in Python?

    • It is an easy and clear way of creating our own iterator.
    • It is memory efficient because it does not store any value it just returns values using yield statement.
    • It is used to produce an infinite stream of data.
    • It can be used to perform pipeline operations.