Learntofish's Blog

A blog about math, physics and computer science

Introduction to generators in Python 3.4

Posted by Ed on April 22, 2015

In this blog post I will explain how generators are used in Python 3.4.



1. Printing square numbers by creating a list
Suppose we wanted to print the first ten square numbers. Here is how we can do it in Python 3.4:

def squares_list(n):
    return [ i*i for i in range(1, n+1) ]

squares = squares_list(10)
 
for num in squares:
    print(num)   

The output is:

1
4
9
16
25
36
49
64
81
100

Obviously we created a list and therefore used up some memory. Is it possible to do the same without creating a list and allocating that extra memory, and by same I mean we retain this part of the code:

for num in squares:
    print(num)   



2. Printing square numbers by creating a generator
Indeed it is possible with a so-called generator. Let’s rewrite the code from above using a generator:

def squares_gen(n):
    i = 1
    while i <= n:
        yield i*i
        i += 1
    
squares = squares_gen(10)
 
for num in squares:
    print(num)  

We get the same output as previously with the advantage that we did not have allocate memory to create a list.



3. Understanding the generator
How does the generator squares_gen work? Let’s have a look at the following code:

def squares_gen(n):
    i = 1
    while i <= n:
        yield i*i
        i += 1
    
squares = squares_gen(10)
 
num = next(squares)
print(num)

num = next(squares)
print(num)

num = next(squares)
print(num)

num = next(squares)
print(num)

The output is:

1
4
9
16

When we call next(squares) the first time, the code inside of squares_gen is executed until it encounters the keyword yield. The value 1*1 is then returned to num and the generator squares_gen pauses.

The second time we call next(squares) the generator resumes from where it paused until it encounters the keyword yield again. The same happens when we call next(squares) the third and fourth time.

Let’s add two print statements to our code to emphasize this pausing behaviour:

def squares_gen(n):
    i = 1
    while i <= n:
        print("right before yield")
        yield i*i
        print("resuming after yield")
        i += 1
    
squares = squares_gen(10)
 
num = next(squares)
print(num, '\n')

num = next(squares)
print(num, '\n')

num = next(squares)
print(num,  '\n')

num = next(squares)
print(num, '\n')

The output is:

right before yield
1 

resuming after yield
right before yield
4 

resuming after yield
right before yield
9 

resuming after yield
right before yield
16 

Be careful though when you call next() because doing so more than ten times will cause an error due to the line squares = squares_gen(10). When we use the syntax for ... in ..., we don’t have to worry about this. All the next() calls happen automatically.



4. Generator instead of list comprehension
Let’s print the first ten cube numbers:

cube_list = [i**3 for i in range(1, 11)] 
for num in cube_list:
    print(num)

Again, we can avoid creating the list by using a generator. In this case we can transform the list comphrehension into a generator by simply replacing the square brackets by parantheses:

cube_gen = (i**3 for i in range(1, 11))
for num in cube_gen:
    print(num)

The output is:

1
8
27
64
125
216
343
512
729
1000



References
1. What can you use Python generator functions for?
2. Behaviour of Python’s “yield”
3. Difference between Python’s Generators and Iterators
4. Generator Expressions vs. List Comprehension



Exercise
Print the first ten numbers in the Fibonacci sequence by using a) a list and b) a generator.



Solution
a) Here is the Python 3.4 code that prints the first ten Fibonacci numbers using a list:

def fibo_list(n):
    a, b = 0, 1
    L = []
    for i in range(n):
        L.append(a)
        a, b = b, a+b
    return L

fibo_nums = fibo_list(10)
for num in fibo_nums:
    print(num)

b) Here is the Python 3.4 code that prints the first ten Fibonacci numbers using a generator:

def fibo_gen(n):
    a, b = 0, 1
    for i in range(n):
        yield a
        a, b = b, a+b
        
fibo_nums = fibo_gen(10)
for num in fibo_nums:
    print(num)

In both a) and b) the output is:

0
1
1
2
3
5
8
13
21
34
Advertisements

One Response to “Introduction to generators in Python 3.4”

  1. […] One reason why this method is more efficient than the first is that itertools.permutations() is a generator, and as such it does not allocate additional memory for a […]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

 
%d bloggers like this: