# 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`.