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