By the end of this lab, you should have submitted the
lab09
assignment using the command submit lab09
.
This lab is due by 11:59pm on 7/24/2014.
Here is a lab09.py starter file for this lab.
for
loop. Why does each
work or not work?
class IteratorA(object):
def __init__(self):
self.start = 5
def __next__(self):
if self.start == 100:
raise StopIteration
self.start += 5
return self.start
def __iter__(self):
return self
class IteratorB(object):
def __init__(self):
self.start = 5
def __iter__(self):
return self
__next__
? This fails to implement the iterator
interface because calling __iter__
doesn't return something that has
a __next__
method.
class IteratorC(object):
def __init__(self):
self.start = 5
def __next__(self):
if self.start == 10:
raise StopIteration
self.start += 1
return self.start
__iter__
method, the for
loop will error. The for
loop needs to
call __iter__
first because some objects might not implement the
__next__
method themselves, but calling __iter__
will return an
object that does.
Watch out on this one. Remember that Ctrl-C is how you stop an infinite loop.
class IteratorD(object):
def __init__(self):
self.start = 1
def __next__(self):
self.start += 1
return self.start
def __iter__(self):
return self
>>> i = Iterator() # Replace with an iterator that works
>>> for item in i:
... print(item)
Then again:
>>> for item in i:
... print(item)
Make sure you understand why you get the output that you get. With
that in mind, try writing an iterator that "restarts" every time it is
run through a for
loop.
class IteratorRestart(object):
"""
>>> i = IteratorRestart(2, 6)
>>> for item in i:
... print(item)
2
3
4
5
>>> for item in i:
... print(item)
2
3
4
5
"""
def __init__(self, start, end):
"*** YOUR CODE HERE ***"
def __next__(self):
"*** YOUR CODE HERE ***"
def __iter__(self):
"*** YOUR CODE HERE ***"
class IteratorRestart(object):
def __init__(self, start, end):
self.start = start
self.end = end
self.current = start
def __next__(self):
if self.current > self.end:
raise StopIteration
self.current += 1
return self.current - 1
def __iter__(self):
self.current = self.start
return self
A generator is a special type of iterator that can be written using a yield
statement:
def <generator_function>():
<somevariable> = <something>
while <predicate>:
yield <something>
<increment variable>
A generator function can also be run through a for
loop:
def generator():
i = 0
while i < 6:
yield i
i += 1
for i in generator():
print(i)
To better figure out what is happening, try this:
def generator():
print("Starting here")
i = 0
while i < 6:
print("Before yield")
yield i
print("After yield")
i += 1
>>> g = generator()
>>> g
___ # what is this thing?
>>> g.__iter__()
___
>>> g.__next__()
___
>>> g.__next__()
____
Trace through the code and make sure you know where and why each statement is printed.
You might have noticed from the Iterators section that the Iterator
defined without a __next__
method failed to run in the for
loop.
However, this is not always the case.
class IterGen(object):
def __init__(self):
self.start = 5
def __iter__(self):
while self.start < 10:
self.start += 1
yield self.start
for i in IterGen():
print(i)
Think for a moment about why that works.
Think more.
Longer.
Okay, I'll tell you.
The for
loop only expects the object returned by __iter__
to have a
__next__
method, and the __iter__
method is a generator function in
this case. Therefore, when __iter__
is called, it returns a
generator object, which you can call __next__
on.
Write a generator that counts down to 0.
Write it in both ways: using a generator function on its own, and
within the __iter__
method of a class.
def countdown(n):
"""
>>> for number in countdown(3):
... print(number)
...
3
2
1
0
"""
"*** YOUR CODE HERE ***"
while n >= 0:
yield n
n = n - 1
class Countdown(object):
"""
>>> counter = Countdown(3)
>>> hasattr(counter, '__next__')
False
>>> for number in counter:
... print(number)
...
3
2
1
0
"""
"*** YOUR CODE HERE ***"
def __init__(self, cur):
self.cur = cur
def __iter__(self):
while self.cur > 0:
yield self.cur
self.cur -= 1
Write a generator function that outputs the hailstone sequence from homework 1.
def hailstone(n):
"""
>>> type(hailstone(10))
<class 'generator'>
>>> for num in hailstone(10):
... print(num)
...
10
5
16
8
4
2
1
"""
"*** YOUR CODE HERE ***"
i = n
while i > 1:
yield i
if i % 2 == 0:
i //= 2
else:
i = i * 3 + 1
yield i
Write a generator function pairs
that takes a list and yields all
the possible pairs of elements from that list.
Note that this means that you should be yielding a tuple.
def pairs(lst):
"""
>>> type(pairs([3, 4, 5]))
<class 'generator'>
>>> for x, y in pairs([3, 4, 5]):
... print(x, y)
...
3 3
3 4
3 5
4 3
4 4
4 5
5 3
5 4
5 5
"""
"*** YOUR CODE HERE ***"
for i in lst:
for j in lst:
yield i, j
Now write an iterator that does the same thing. You are only allowed to use a linear amount of space - so computing a list of all of the possible pairs is not a valid answer. Notice how much harder it is - this is why generators are useful.
class PairsIterator:
"""
>>> for x, y in PairsIterator([3, 4, 5]):
... print(x, y)
...
3 3
3 4
3 5
4 3
4 4
4 5
5 3
5 4
5 5
"""
def __init__(self, lst):
"*** YOUR CODE HERE ***"
def __next__(self):
"*** YOUR CODE HERE ***"
def __iter__(self):
"*** YOUR CODE HERE ***"
def __init__(self, lst):
self.lst = lst
self.i = 0
self.j = 0
def __next__(self):
if self.i == len(self.lst):
raise StopIteration
result = self.lst[self.i], self.lst[self.j]
if self.j == len(self.lst) - 1:
self.i += 1
self.j = 0
else:
self.j += 1
return result
def __iter__(self):
return self