Concepts
- What method(s) must iterables have?
- What method(s) must iterators have?
- What data type does
__iter__
return? - What data type does
__next__
return? - Is a generator an iterable or an iterator?
- What keyword in the body of a function makes that function return a generator object?
- How is
yield
different fromreturn
? - When you call
next
on a generator, the body starts executing at what line? At what line will it stop? At what line will it start the next time you callnext
? - What happens when you call
list
on an iterable or an iterator? What happens if you call it a second time on the same objects? - Can you iterate through an iterable in a for loop? Can you iterate through an iterator in a for loop?
__iter__
__iter__
AND__next__
iterator
whatever type each element of the sequence is
iterator
yield
yield
does not close the frame.yield
outputs a value, and keeps the frame open untilStopIteration
is raised.
execution starts at the first line of the body and stops after the line withyield
is executed. whennext
is called again, execution picks up at the line right after theyield
statement (where it last left off) and stops after anotheryield
execution or if there are no more lines in the body.
when you calllist
on an iterable or an iterator, Python callsiter
on it and then attempts to construct a list with thenext
element untilStopIteration
is reached, then returns the constructed list.
you can use both iterables and iterators in a for loop.
What would Python print?
The following classes define an iterable representing the sequence of multiples for any given number and the iterator that returns the next value in the sequence. The sequence goes up to 1000.
class Multiples:
def __init__(self, num):
self.num = num
def __iter__(self):
return MultiplesIterator(self.num)
class MultiplesIterator:
def __init__(self, num):
self.num = num
self.curr = num
def __iter__(self):
return self
def __next__(self):
if self.curr >= 1000:
raise StopIteration
val = self.curr
self.curr = self.curr + self.num
return val
What will the following lines output?
>>> hundreds = Multiples(100)
>>> next(hundreds)
TypeError: 'Multiples' object is not an iterator
>>> next(iter(hundreds))
100
>>> next(iter(hundreds))
100
>>> i = iter(hundreds)
>>> i is iter(hundreds)
False
>>> i is iter(i)
True
>>> next(i)
100
>>> next(i)
200
>>> list(hundreds)
[100, 200, 300, 400, 500, 600, 700, 800, 900]
>>> list(i)
[300, 400, 500, 600, 700, 800, 900]
>>> list(hundreds)
[100, 200, 300, 400, 500, 600, 700, 800, 900]
>>> list(i)
[]
>>> for i in hundreds:
... print(i)
5
25
125
625
>>> for x in i:
... print(x)
Explanation: Multiples
's __iter__
method always returns a *new instance* of MultiplesIterator
, whereas MultiplesIterator
's __iter__
simply returns itself. For iterables, calling iter
will reset the iterator, but since iterators just return themselves, they do not reset.
TOGGLE SOLUTION TOGGLE EXPLANATION
Writing code
Fill in the following definition of a generator function which yields every number from 1
to n
and prints 'm was a factor'
if the previous number, m
, was a factor of n
. See the doctests for an example.
def print_factor(n):
"""
>>> gen = print_factor(8)
>>> next(gen)
1
>>> next(gen)
1 was a factor
2
>>> next(gen)
2 was a factor
3
>>> next(gen)
4
>>> next(gen)
4 was a factor
5
"""
"*** YOUR CODE HERE ***"
def print_factor(n):
curr = 1
while curr <= n:
yield curr
if n % curr == 0:
print(str(curr) + " was a factor")
curr += 1