[Edu-sig] Example Iterator (2.2a)

Kirby Urner pdx4d@teleport.com
Mon, 23 Jul 2001 00:21:03 -0700


In this example, the Test class is designed to act as its
own iterator, meaning it returns self in __iter__, and
backs this up by implementing a next() method -- a requirement
for iterators.

This class accepts a phrase upon initialization, and iteration
simply means returning the next character in the phrase.  You
stop iterating by throwing an exception.  In a for-loop over
the object, this simply terminates the loop, without an error
message:

   >>> from play import Test
   >>> ot = Test("Able was I ere I saw Elba")
   >>> for k in ot:  print k,

   A b l e   w a s   I   e r e   I   s a w   E l b a
   >>>

But then if you try to force an iteration, you'll get a real
exception.

   >>> ot.i
   25
   >>> ot.next()
   Traceback (most recent call last):
     File "<pyshell#286>", line 1, in ?
       ot.next()
     File "D:\PROGRAM FILES\PYTHON22\work\play.py", line 46, in next
       raise StopIteration,"Length of Phrase exceeded"
   StopIteration: Length of Phrase exceeded

For-loops, though, simply return nothing, now that we're at the
end of our rope.  The value of ot.i will keep increasing in this
example, even though the for-loop is mute (because of where the
exception is raised in this class).

   >>> for k in ot:  print k,

   >>>

The __iter__ method supercedes using __getitem__ to support
iteration in a for-loop.  Using __getitem__ wasn't optimal,
because your object might not support random access (as per
PEP), i.e. you want to be able to iterate through a file-like
object, but file[10] (dropping in on the 10th line) might
not be something you want to implement (or even could if you
wanted to).

In other words, iteration and indexing aren't quite the
same concept.  Getting a next() element isn't the same as
being able to jump right away to just *any* as in
object[any int].

Notice that the iterator of ot is ot itself in this case:

   >>> iter(ot)
   <play.Test instance at 013EBE1C>
   >>> ot
   <play.Test instance at 013EBE1C>

If, just for the heck of it, we were to return primes() in
the __iter__ method, then we could still call ot.next()
explicitly, but any implicit invocations of ot's iterator
would invoke our primes generator function (a generator
is able to work as an iterator, i.e. is like any user
class which both supports next() and defines itself as
an iterator).  Looking at primes() again:

   >>> p
   <generator object at 00BCE80C>
   >>> p.__iter__
   <method-wrapper object at 01398650>
   >>> p.next
   <method-wrapper object at 013B1110>

And now looking at ot, an instantiation of Test wherein
__iter__ has been redefined to return primes():

   >>> ot = Test("Able was I ere I saw Elba")
   >>> ot.next()
   'A'
   >>> ot.next()
   'b'
   >>> for i in ot:
     	  print i,
	  if i>100:  break

	
   2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61
   67 71 73 79 83 89 97 101

Kirby

More from play.py:
=====================

class Test:

     def __init__(self,phrase):
         self.phrase = phrase
         self.i = -1

     def __iter__(self):
         return self
         # return primes() -- alternative generator object

     def next(self):
         self.i += 1
         if self.i>len(self.phrase)-1:
             raise StopIteration,"Length of Phrase exceeded"
         return self.phrase[self.i]