Iterators and Generators: basic-level questions

If you need help reviewing Iterators and Generators, take a look at these resources:

Each question has a "Toggle Solution" button -- click it to reveal that question's solution.

Iterators: Conceptual questions

Given any object obj, what special method will iter(obj) call? What type of object will it return?

The built-in Python function iter will implicitly call obj.__iter__, a method. This method will return an Iterator object, which is any object that has a __next__ method.

Question 1

What is wrong with the following code?

>>> obj = SomeObj()
>>> i = iter(obj)
>>> next(obj)

obj is not necessarily an iterator, so you should not call next on it. next should be called on i instead.

NOTE: even if the __iter__ method of SomeObj returns self, you still should not call next on obj. This is to protect abstraction barriers.

Iterators: Code-Writing Questions

Question 2

Write an iterator for a Fibonacci class. The iterator should return the next Fibonacci number every time next is called on it.

class Fibonacci:
    """Doctests

    >>> f = Fibonacci()
    >>> i = iter(f)
    >>> next(i)
    0
    >>> next(i)
    1
    >>> next(i)
    1
    >>> next(i)
    2
    """
    "*** YOUR CODE HERE ***"
class Fibonacci:
    def __init__(self):
        self.cur, self.next = 0, 1

    def __iter__(self):
        return self

    def __next__(self):
        tmp = self.cur
        self.cur, self.next = self.next, self.next + self.cur
        return tmp

Generators: Conceptual questions

Question 3

Given the following generator function, what will the call to gen() return?

def gen():
    start = 0
    while start != 10:
        yield start
        start += 1

gen() will return a generator object, NOT the number 0. None of the code inside the generator function will be executed.

Question 4

When does a generator raise a StopIteration exception?

When the end of the generator function is reached.

Generators: Code-Writing questions

Question 5

Write a generator function map_gen that takes a one-argument function and an iterator as arguments. The return result should be a generator whose elements are the elements of the iterator, but with the function mapped onto them.

def map_gen(fn, iter1):
    """Doctests

    >>> i = iter([1, 2, 3, 4])
    >>> fn = lambda x: x**2
    >>> m = map_gen(fn, i)
    >>> next(m)
    1
    >>> next(m)
    4
    >>> next(m)
    9
    >>> next(m)
    16
    >>> next(m)
    Traceback (most recent call last):
      ...
    StopIteration
    """
    "*** YOUR CODE HERE ***"
def map_gen(fn, iter1):
    for elem in iter1:
        yield fn(elem)

Since iter1 is an iterator, we can iterate over it in a for loop.

Question 6

Write another iterator for a Fibonacci class. Like before, the iterator should return the nxt Fibonacci number every time next is called on it. This time, write the iterator using a generator function.

class Fibonacci:
    """Doctests

    >>> f = Fibonacci()
    >>> i = iter(f)
    >>> next(i)
    0
    >>> next(i)
    1
    >>> next(i)
    1
    >>> next(i)
    2
    """
    "*** YOUR CODE HERE ***"
class Fibonacci:
    def __iter__(self):
        cur, next = 0, 1
        while True:
            yield cur
            cur, next = next, cur + next

The generator in the __iter__ method can keep track of state, so we don't need to initialize anything. We also don't need to write a __next__ method, since the __iter__ method is not returning self.