Generators are iterators, a kind of iterable you can only iterate over once.
What are iterators anyway?
An iterator is an object that can be iterated (looped) upon. It is used to abstract a container of data to make it behave like an iterable object. Some common iterable objects in Python are – lists, strings, dictionary.
Every generator is an iterator, but not vice versa. A generator is built by calling a function that has one or more yield expressions.
The yield keyword works like return which means that values that are yielded get “returned” by the generator. Unlike return, the next time the generator gets asked for a value, the generator’s function, resumes where it left off after the last yield statement and continues to run until it hits another yield statement.
Generator is simply a function that return a generator object on which you can call next() such that for every call it returns some value until it raises a StopIteration exception, signaling that all values have been generated.
Let’s start with creating some generators
def test_generator():
yield "First iterator"
yield "Second iterator"
yield "Third iterator"
The generator function creates a generator object you can verify this
test_generator()
Output:
<generator object test_generator at 0x5f08705675258>
Store this object in a variable and call the next()
method on it. Every call on next()
will yield a single value until all the values have been yield.
generator_object = test_generator()
next(generator_object)
Output:
First iterator
Again from the definition, every call to next will return a value until it raises a StopIteration
exception, signaling that all values have been generated so for this example we can call the next method 3 times since there are only 3 yield statements to run.
2nd Iteration
next(generator_object)
Output:
Second iterator
3rd Iteration
next(generator_object)
Output:
Third iterator
If you call next(generator_object)
for the fourth time, you will receive StopIteration
error from the Python interpreter.
Let’s look at another example.
import random
def random_number_generator():
while True:
number = random.randint(0,1000)
yield number
Here the generator function will keep returning a random number since there is no exit condition from the loop.
num = random_number_generator()
next(num)
Output:
875
Advantages of Generators
Unlike with regular functions where each time the stack frame is discarded, you lose all that “state”. Also, generators do not store all the values in memory instead they generate the values on the fly thus making the ram more memory efficient.
Conclusion
Generator functions are ordinary functions defined using yield instead of return. When called, a generator function returns a generator object, which is a kind of iterator – it has a next()
method. When you call next()
, the next value yielded by the generator function is returned.