Generators are used to create iterators. Simply speaking, a generator is a function that returns an object (iterator) which we can iterate over (one value at a time). If a function contains at least one yield statement (it may contain other yield or return statements), it becomes a generator function. Both yield and return will return some value from a function.
Generator function vs Normal function
Difference between generator function and normal function.
- Generator function contains one or more yield statement.
- When called, it returns an object (iterator) but does not start execution immediately.
- Methods like __iter__() and __next__() are implemented automatically. So we can iterate through the items using next().
- Once the function yields, the function is paused and the control is transferred to the caller.
- Local variables and their states are remembered between successive calls.
# A simple generator function def my_gen(): n = 1 print('This is printed first') # Generator function contains yield statements yield n n += 1 print('This is printed second') yield n n += 1 print('This is printed at last') yield n # Returns an object but does not start execution immediately. a = my_gen() # We can iterate through the items using next(). next(a) # Once the function yields, the function is paused and the control is transferred to the caller. # Local variables and theirs states are remembered between successive calls. next(a) next(a) # Finally, when the function terminates, StopIteration is raised automatically on further calls. next(a) # Output This is printed first This is printed second This is printed at last Traceback (most recent call last): next(a) StopIteration
my_gen() looks like a typical function definition, except for the Python yield statement and the code that follows it. yield indicates where a value is sent back to the caller, but unlike return, you don’t exit the function afterward.
Generators with Loop
Generator functions are implemented with a loop having a suitable terminating condition.
def rev_str(my_str): length = len(my_str) for i in range(length - 1,-1,-1): yield my_str[i] for char in rev_str("hello"): print(char) # Output o l l e h
Simple generators can be created on the fly using generator expressions. Same as lambda function creates an anonymous function, generator expression creates an anonymous generator function. They are kind of lazy, producing items only when asked for. For this reason, a generator expression is much more memory efficient than an equivalent list comprehension.
# Initialize the list my_list = [1, 3, 6, 10] # square each term using list comprehension a = [x**2 for x in my_list] # same thing can be done using generator expression a = (x**2 for x in my_list) # Output: 1 print(next(a)) # Output: 9 print(next(a)) # Output: 36 print(next(a)) # Output: 100 print(next(a)) # Output: StopIteration next(a)