Entry
How can I make an iterator for lists with features like advance/regress or failfast?
How can I advance/regress in a for loop?
Aug 2nd, 2002 08:56
Michael Chermside,
Suppose you have a loop like this:
li = [1,1,2,3,3,4]
for item in li:
<code here>
One of the things that you CAN'T do is to back up or skip forward.
(Actually, "continue" can be used to skip forward by 1 item, but only
one.) Another thing that you SHOULDN'T do is to modify the list in the
for loop... all kinds of messy things could happen (try remove()'ing
items and see what it does).
The traditional solution to this is to break down and use index arithmatic.
li = [1,1,2,3,3,4]
for index in range(len(li)):
<code here>
But sometimes this just isn't as elegant (although at least it's
simple). So another approach would be to create an iterator which has
the abilities you want already built in.
The following iterator will allow you to advance or regress through the
list, and it performs a very basic level of testing for list
modification (taking a fail-fast approach). It is used like this:
li = [1,1,2,3,3,4]
for item in MovableListIterator(li):
<code>
After the code, you'll find test code (always a good idea of course!).
This has been tested and works well with Python 2.2.
class MovableListIterator:
"""This is an iterator for lists which allows one to advance
or regress the position in the list. It also will raise an
exception if the list is lengthened or shortened."""
def __init__(self, myList):
self.myList = myList
self.originalLength = len(myList)
self.nextIndex = 0
def __iter__(self):
return self
class ListModifiedException(Exception):
pass
def next(self):
if len(self.myList) != self.originalLength:
raise MovableListIterator.ListModifiedException
index = self.nextIndex
if index >= self.originalLength:
raise StopIteration
self.nextIndex += 1
return self.myList[index]
def advance(self, places=1):
self.nextIndex += places
def regress(self, places=1):
self.nextIndex -= places
import unittest
class TestMovableListIterator(unittest.TestCase):
def test_create(self):
li = list('abcde')
i = MovableListIterator(li)
def test_basic_loop(self):
li = 'abcde'
self.assertEqual( list(li), [x for x in MovableListIterator(li)] )
def test_advance(self):
li = 'abcdef'
result = []
i = MovableListIterator(li)
result.append( i.next() )
i.advance()
result.append( i.next() )
i.advance(2)
result.append( i.next() )
self.assertEqual( list('acf'), result )
self.assertRaises( StopIteration, i.next )
def test_regress(self):
li = 'abc'
result = []
i = MovableListIterator(li)
result.append( i.next() )
i.regress()
result.append( i.next() )
result.append( i.next() )
result.append( i.next() )
i.regress(2)
result.append( i.next() )
result.append( i.next() )
self.assertEqual( list('aabcbc'), result )
self.assertRaises( StopIteration, i.next )
def test_shrink_throws(self):
li = [1,2,3,4]
i = MovableListIterator(li)
li.pop()
self.assertRaises( MovableListIterator.ListModifiedException,
i.next )
def test_grow_throws(self):
li = [1,2,3,4]
i = MovableListIterator(li)
li.append(5)
self.assertRaises( MovableListIterator.ListModifiedException,
i.next )