__next__ and StopIteration

Chris Angelico rosuav at gmail.com
Mon Feb 9 23:46:25 EST 2015


On Tue, Feb 10, 2015 at 3:33 PM, Charles Hixson
<charleshixsn at earthlink.net> wrote:
>> 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.
>
> So if I'm understanding this correctly, I should implement as an internal
> class within Grid something like:
>     class GridIter(Iterator):

Apart from the fact that you shouldn't have to explicitly subclass
Iterator, yes. But this is the hard way to do things. The easy way is
to simply define an __iter__ method on your Grid which returns an
iterator - and one excellent form of iterator, for custom classes like
this, is a generator object. Your original code can slot happily in,
with one tiny change:

class Grid:
    blah blah

    def __iter__(self):
        for row in range(self._rows):
            for col in range(self._cols):
                if self._grid[row][col]:
                    yield self._grid[row][col]

The only change is to remove the explicit StopIteration at the end;
once your generator function terminates, the generator object will
raise StopIteration forever afterward, without any help from you.
(Also, post-PEP479, the explicit raise will actually cause
RuntimeError, so it's not just superfluous, but actually a problem.)

But I'm guessing that your grid and rows are actually iterable
themselves. If they are, you can cut the code down to this:

    def __iter__(self):
        for row in self._grid:
            for cell in row:
                if cell: yield cell

or a generator expression:

    def __iter__(self):
        return (cell for row in self._grid for cell in row if cell)

or itertools.chain and filter, if you so desired. As long as you
return an iterator, you're fine. This is far and away the easiest way
to make a class iterable.

ChrisA



More information about the Python-list mailing list