[Tutor] _next

Roel Schroeven rschroev_nospam_ml at fastmail.fm
Thu May 25 12:55:28 CEST 2006


Christopher Spears schreef:
> How does this script work?
> 
> #!/usr/bin/python
> 
> class IteratorExample:
>     def __init__(self, s):
>         self.s = s
>         self.next = self._next().next
>         self.exhausted = 0
>     def _next(self):
>         if not self.exhausted:
>             flag = 0
>             for x in self.s:
>                 if flag:
>                     flag = 0
>                     yield x
>                 else:
>                     flag = 1
>             self.exhausted = 1
>     def __iter__(self):
>         return self._next()
> 
> def main():
>     a = IteratorExample('edcba')
>     for x in a:
>         print x

Maybe I should try to explain what's happening instead of only offering 
an alternative. It's somewhat complicated by the fact that this is a 
very convoluted example IMO.

To be able to use an object in a for loop like that, it needs an 
__iter__() method. That method is called behind the scenes when the for 
loop is started, and it should return an iterator object. An iterator 
object is an object with a next() method that returns the next element 
each time it's called, and raises StopIteration if there are no more 
elements.

Useless but simple example:

class Count:
     def __init__(self):
         self.max = 10
         self.index = 0
     def __iter__(self):
         return self
     def next(self):
         self.index += 1
         if self.index > self.max:
             raise StopIteration
         return self.index

Objects instantiated from this class can be iterated over because of the 
__iter__() method. In this case, that method returns the object itself; 
therefore the object itself serves as the iterator object. It does this 
with its next() method, which simply returns the numbers 1, 2, 3, 4, 5, 
6, 7, 8, 9, 10 and then raises StopIteration.

A special kind of iterator object can be made with a generator function. 
That looks like a normal function, but uses yield instead of return. 
Each time its next element is requested, it runs until it encounters a 
yield statement. At that point, it passes the value specified in the 
yield statement to its caller and then freezes. When the next element is 
requested, it resumes operation directly after the yield statement where 
it was frozen until it encounters the next yield statement. And so on, 
until the function ends or executes the return statement or raises 
StopIteration. Again, a simple example:

def backwards(s):
     for x in s[::-1]:
         yield x

for x in backwards('edcba'):
     print x

a
b
c
d
e

Now, in your example the two are mixed. To make matters somewhat less 
complicated, I commented some lines that are totally useless; you should 
ignore them:

class IteratorExample:
     def __init__(self, s):
         self.s = s
         #self.next = self._next().next
         #self.exhausted = 0
     def _next(self):
         #if not self.exhausted:
             flag = 0
             for x in self.s:
                 if flag:
                     flag = 0
                     yield x
                 else:
                     flag = 1
             #self.exhausted = 1
     def __iter__(self):
         return self._next()


The _next() method is a generator function as I described above, which 
creates an iterator object when called.
The __iter__() method just calls that generator function and returns the 
result to its caller.

HTH

-- 
If I have been able to see further, it was only because I stood
on the shoulders of giants.  -- Isaac Newton

Roel Schroeven



More information about the Tutor mailing list