Session 10 - Generators

Today you will learn how to make your classes iterable. You will learn how to create a generator function and how to write this in an easier to read manner using a generator expression. You will also gain inside into why a function in python is an object and how to make your own object callable.

We will look at Iterator classes

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class Compute:
    def __iter__(self):
            self.last = 0
            return self

    def __next__(self):
            rv = self.last
            self.last += 1
            if self.last > 10:
                raise StopIteration()
            sleep(.5)
            return rv

 for i in Compute():
     print(i)

And see how it can be done in an easier to read and use manner with a generator function

1
2
3
def compute():
     for i in range(10):
        yield i

And write a generator expression.

1
(i for i in range(10))

Learning goals

  • Understand how functions are abstractions of a class.

  • Create memory and time efficient code using:
    • Iterator Classes

    • generator functions and

    • generator expressoions.

Materials

Exercises

ex1: Python Students

Solution

Based on the Student class below, create a PythonStudents class that acts as a collection of students. The class should implement the iterations protocol (iter(), next() and StopIteration). When iterated the Pythod_students object should return the name of each student in the list.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
  class PythonStudents:
    pass




  class Student:

     def __init__(self, name, cpr):
        self.name = name
        self.cpr = cpr

     @property
     def name(self):
             return self.__name

     @name.setter
     def name(self, name):
             self.__name = name.capitalize()

     def __add__(self, student):
             return Student('Anna the daugther', 1234)

     def __str__(self):
             return f'{self.name}, {self.cpr}'

     def __repr__(self):
             return f'{self.__dict__}'

ex2: School of students

Solution

In this exercise you start out by having a list of names, and a list of majors.

Your job is to create:

  1. A list of dictionaries of students (ie: students = [{‘id’: 1,’name’: ‘Claus’, ‘major’: ‘Math’}]), cretated in a normal function that returns the result.

  2. A Generator that “returns” a generator object. So the student is yield instead of returned.

Both functions should do the same, but one returns a list and one a generator object.

students = [{‘id’: 1,’name’: ‘Clasu’, ‘major’: ‘Math’}]
The id could be generated by a counter or like in a loop.
The Name should be found by randomly chosing a name from the names list
The Major should be found by randomly chosing a major from the major list
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
names = ['John', 'Corey', 'Adam', 'Steve', 'Rick', 'Thomas']
majors = ['Math', 'Engineering', 'CompSci', 'Arts', 'Business']

def students_list(num_students):
    pass

def students_generator(num_students):
    pass

people = students_list(1000000)
people = students_generator(1000000)

ex3: Range Mimic

Solution

  1. Create a “clone” of the build in range() function, by doing an iterator class. Besides implementing the protocol for beeing iterable, this class should also be callable in order for it to be used like this.

First try this in your interpreter to get inspired:

>>> r = range(1, 10, 2)
>>> next(r)
TypeError: 'range' object is not an iterator
>>> i = iter(r)
>>> next(i)
1
  1. Now do the same, but use a generator function instead.