iterators and views of lists
Steven D'Aprano
steve at REMOVE-THIS-cybersource.com.au
Fri Dec 18 20:12:18 EST 2009
On Fri, 18 Dec 2009 10:39:15 -0800, Carl Banks wrote:
> It is true that Python iterators can't be used to mutate the underlying
> structure--if there is actual underlying data structure--
An iterator is a protocol. So long as you have __iter__ and next (or
__next__ in Python 3) methods, your class is an iterator. What you do in
those methods are up to you. If you want to mutate the underlying data,
you're free to do so.
Just for chuckles, here's a quick-and-dirty iterator version of the Tower
of Hanoi that mutates itself as needed. (I make no promise that it is
either efficient or bug-free.)
class Hanoi:
def __init__(self, n=5):
self.a = range(n, 0, -1)
self.b = []
self.c = []
self.num = 0
def __str__(self):
template = "Needle %d: %s"
d = [template % (i, data) for (i,data) in
enumerate([self.a, self.b, self.c])]
return '\n'.join(d) + '\n'
def __iter__(self):
return self
def next(self):
if [] == self.a == self.c:
raise StopIteration
self.num += 1
if self.num % 2:
# Odd-numbered move: move the smallest disk one
# position clockwise.
needle = self.find_smallest()
nxt = "bca"["abc".find(needle)]
else:
# Even-numbered move: make the only legal move of the
# next-smallest disk.
needle, nxt = self.find_legal()
self.move(needle, nxt)
return str(self)
def move(self, frm, to):
"""Move the top disk from needle `frm` to needle `to`."""
from_needle = getattr(self, frm)
to_needle = getattr(self, to)
to_needle.append(from_needle[-1])
del from_needle[-1]
def find_smallest(self):
for needle in 'abc':
if 1 in getattr(self, needle):
return needle
def find_legal(self):
"""Find the only legal move not using the smallest disk."""
ignore = self.find_smallest()
if ignore == 'a': disks = 'bc'
elif ignore == 'b': disks = 'ac'
else: disks = 'ab'
left_needle = getattr(self, disks[0])
right_needle = getattr(self, disks[1])
if left_needle == []:
return disks[1], disks[0]
elif right_needle == []:
return disks[0], disks[1]
elif left_needle[-1] < right_needle[-1]:
return disks[0], disks[1]
else:
return disks[1], disks[0]
And in use:
>>> p = Hanoi()
>>> for state in p:
... print state
...
Needle 0: [5, 4, 3, 2]
Needle 1: [1]
Needle 2: []
Needle 0: [5, 4, 3]
Needle 1: [1]
Needle 2: [2]
[ rest of output skipped for brevity ]
Python iterators don't generally mutate the thing they are iterating
over, not because they can't, but because it's generally a bad idea to do
so. In general, it really only makes sense as an optimization in
situations where you have so much data that you can't afford to make a
copy of it. For the occasional time where it does make sense, Python
iterators are perfectly capable of doing so.
--
Steven
More information about the Python-list
mailing list