Python Generators: How To Create A Generator in Python?
Python Generators: How To Create A Generator in Python?
This is both lengthy and counter intuitive. Generator comes into rescue in such situations.
Python generators are a simple way of creating iterators. All the overhead we mentioned above are automatically handled
by generators in Python.
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 returnstatements), it becomes a generator
function. Both yield and return will return some value from a function.
The difference is that, while a return statement terminates a function entirely, yieldstatement pauses the function saving all
its states and later continues from there on successive calls.
Here is an example to illustrate all of the points stated above. We have a generator function named my_gen() with
several yield statements.
def my_gen():
n=1
yield n
n += 1
yield n
n += 1
yield n
An interactive run in the interpreter is given below. Run these in the Python shell to see the output.
>>> # Local variables and theirs states are remembered between successive calls.
>>> next(a)
This is printed second
2
>>> next(a)
This is printed at last
3
>>> # Finally, when the function terminates, StopIteration is raised automatically on further calls.
>>> next(a)
Traceback (most recent call last):
...
StopIteration
>>> next(a)
Traceback (most recent call last):
...
StopIteration
One interesting thing to note in the above example is that, the value of variable n is remembered between each call.
Unlike normal functions, the local variables are not destroyed when the function yields. Furthermore, the generator object
can be iterated only once.
To restart the process we need to create another generator object using something like a = my_gen().
Note: One final thing to note is that we can use generators with for loops directly.
This is because, a for loop takes an iterator and iterates over it using next() function. It automatically ends
when StopIteration is raised. Check here to know how a for loop is actually implemented in Python.
def my_gen():
n=1
yield n
n += 1
yield n
n += 1
yield n
print(item)
Normally, generator functions are implemented with a loop having a suitable terminating condition.
def rev_str(my_str):
length = len(my_str)
yield my_str[i]
# Output:
#o
#l
#l
#e
#h
print(char)
Same as lambda function creates an anonymous function, generator expression creates an anonymous generator function.
The syntax for generator expression is similar to that of a list comprehension in Python. But the square brackets are
replaced with round parentheses.
The major difference between a list comprehension and a generator expression is that while list comprehension produces
the entire list, generator expression produces one item at a time.
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.
We can see above that the generator expression did not produce the required result immediately. Instead, it returned a
generator object with produces items on demand.
my_list = [1, 3, 6, 10]
# Output: 1
print(next(a))
# Output: 9
print(next(a))
# Output: 36
print(next(a))
# Output: 100
print(next(a))
# Output: StopIteration
next(a)
Generator expression can be used inside functions. When used in such a way, the round parentheses can be dropped.
class PowTwo:
def __init__(self, max = 0):
self.max = max
def __iter__(self):
self.n = 0
return self
def __next__(self):
if self.n > self.max:
raise StopIteration
result = 2 ** self.n
self.n += 1
return result
This was lengthy. Now lets do the same using a generator function.
Since, generators keep track of details automatically, it was concise and much cleaner in implementation.
2. Memory Efficient
A normal function to return a sequence will create the entire sequence in memory before returning the result. This is an
overkill if the number of items in the sequence is very large.
Generator implementation of such sequence is memory friendly and is preferred since it only produces one item at a time.
The following example can generate all the even numbers (at least in theory).
def all_even():
n = 0
while True:
yield n
n += 2