Finally figured out member generators...

Steven Taschuk staschuk at telusplanet.net
Wed Mar 5 02:42:06 CET 2003


Quoth Bjorn Pettersen:
  [...]
> >>> class E:
> ...     def __init__(self):
> ...         self.value = range(10)
> ...         self.scale = 5
> ...     def __iter__(self):
> ...         self.cur = 0
> ...         return self
> ...     def next(self):
> ...         pos = self.cur
> ...         self.cur += 1
> ...         if pos < len(self.value):
> ...             return self.value[pos] * self.scale
> ...         else:
> ...             raise StopIteration
> ...
> >>> e = E()
> >>> list(e)
> [0, 5, 10, 15, 20, 25, 30, 35, 40, 45]

This implementation has the unusual property of being implicitly
reset by attempts to iterate over it.  This can be surprising if
one is used to the normal iterators; for instance:

    >>> it = iter(range(10))
    >>> for value in it:
    ...     if value > 5:
    ...         break
    ... 
    >>> value
    6
    >>> list(it)
    [7, 8, 9]

Note that this normal iterator preserves its state between
iterations over it.  Among other things, this makes it possible to
consume some elements from an iterator, then pass it to some other
code for processing of the remaining elements.

Instances of your class E, however, do not behave this way:

    >>> it = iter(E())
    >>> for value in it:
    ...     if value > 20:
    ...         break
    ... 
    >>> value
    25
    >>> list(it)
    [0, 5, 10, 15, 20, 25, 30, 35, 40, 45]

Afaik the docs don't explicitly prescribe the normal behaviour,
but imho it's the most natural way for an iterator to behave, and
writing an iterator which behaves differently is asking for
trouble.

I don't mean to suggest that iterable objects should never be
their own iterators; on the contrary, for certain stateful objects
this is quite natural.  But even for such objects, calling
__iter__ should imho not reset the iteration; if resetting makes
sense for the object in question, it should be a separate method
that the client code must explicitly call.

> The problem with the exmple the top, was simply that by specifying self
> as the __iter__, I was implying that d.next() would return the next item
> in D [...]
> The reality is, of course, that d.next() returns not an item in D, but
> an (generator-)iterator (an object containing next() that will produce
> the items).

Exactly.

> [... how to use separate iterator classes..]
> 
> I agree that separate iterator classes are very explicit and have a
> number of good qualities, they seem heavy-handed when it comes to Python
  [...]

Absolutely.  That example was just for explanatory purposes, to
illustrate that the iterable and iterator contracts are
conceptually separate.  In practice a generator function with its
implicit iterator is usually more pleasing (if only because, in
2.2.2 at least, it requires a future statement, which is the
closest PSU wannabes like me get to real time travel).

-- 
Steven Taschuk                                                 o- @
staschuk at telusplanet.net                                      7O   )
                                                               "  (





More information about the Python-list mailing list