__next__ and StopIteration

Steven D'Aprano steve+comp.lang.python at pearwood.info
Mon Feb 9 19:42:52 EST 2015


Ian Kelly wrote:

> On Mon, Feb 9, 2015 at 4:30 PM, Steven D'Aprano
> <steve+comp.lang.python at pearwood.info> wrote:
[...]
>> Your class is itself an iterator.
> 
> This is an anti-pattern, so don't even suggest it. Iterables should
> never be their own iterators. Otherwise, your iterable can only be
> iterated over once!

Hmmm, good point.

However, I will point out a couple of factors:

Ultimately, that's the correct behaviour for *iterator* classes. With the
rich set of tools available to build iterators from built-in parts, it is
rare that you need to write your own class with a __next__ method, but if
you do, that's the way you want it to behave.

Whether that *iterator* class should be the same class as the *iterable*
class is another story. In the built-ins, they mostly (always?) come in
pairs:

tuple <-> tuple_iterator
list <-> list_iterator
dict <-> dict_keyiterator
set <-> set_iterator
range <-> range_iterator

so that's an excellent sign that doing so is best practice, but it should
not be seen as *required*. After all, perhaps you have good reason for
wanting your iterable class to only be iterated over once.

Also, *technically* iterators may be re-iterable. The docs say that
iterators which fail to raise StopIteration forever once they are exhausted
are "broken", but the docs do not forbid broken iterators. Consenting
adults and all that. You might want an iterator with a reset() method. Even
an outright broken iterator!

    def __next__(self):
        if random.random() < 0.1: raise StopIteration
        return random.random()

Why you would want one, I don't know, but if you have a hankering for such a
beast, Python lets you do it.


> The proper version of the "hard way" is:
> 
> 1) The __iter__ method of the iterable constructs a new iterator
> instance and returns it.
> 
> 2) The __iter__ method of the *iterator* simply returns itself.
> 
> 3) The __next__ method of the iterator tracks the current value and
> returns the next value. Note that the iterator should never store the
> iterable's data internally, unless the iterable is immutable and the
> calculation is trivial (e.g. a range object). Instead, it should
> determine the next value by referring to its source iterable.


-- 
Steven




More information about the Python-list mailing list