[Tutor] iter class

Steven D'Aprano steve at pearwood.info
Fri Jan 24 01:43:48 CET 2014


On Thu, Jan 23, 2014 at 02:24:20PM -0500, Keith Winston wrote:
> On Thu, Jan 23, 2014 at 7:05 AM, eryksun <eryksun at gmail.com> wrote:
> > Generally you'll make `__iter__` a generator, so you don't have to
> > worry about implementing `__next__`. Also, the built-in function
> > `next` was added in 2.6, so you don't have to worry about the method
> > name difference between 2.x and 3.x, either.
> 
> I'm now realizing I don't understand this comment at all. First, there
> IS a __iter__ method in the example: does the presence of such make it
> a generator already, or is it a usage thing, or something else? I
> don't yet completely understand the difference/point... or maybe you
> mean inherit the generator class?

Generators are a kind of function, which are special. You can't inherit 
from them:

py> def generator():
...     yield 1
...
py> class MyGenerator(type(generator)):
...     pass
...
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: type 'function' is not an acceptable base type


although I expect that should probably be considered an implementation 
detail of CPython rather than a language features. (Some other Pythons 
may allow you to inherit from function, if they wanted to.)

Eryksun means that when you write an iterable class, you might be 
tempted to manage the next method yourself, but nine times out of ten 
you don't need to, you can delegate the annoying part of the coding to 
Python. That is, instead of creating an *actual* iterator:

# Pseudocode
class MyIterator:
    def __iter__(self):
        return self
    def __next__(self):
        # I have to manage this myself...
        if there still are items to process:
            return the next item
        else:
            raise StopIteration


you can *return* an iterator. This makes your class a mock or 
pseudo-iterator, I suppose, although for the most part only pedants 
normally worry about the difference and we normally call it an iterator 
and hope nobody gets confused. But for now, I'm going to be pedantic and 
be absolutely firm on the difference:

class MyPseudoIterator:
    def __iter__(self):
        return iter(some data)


(It's not a true iterator, because it doesn't return self. Instead, it 
returns a true iterator. But 95% of the time, we don't care too much 
about the difference.)

Sometimes you already have a bunch of data ready to use, like a list, 
and calling iter() is easy. But sometimes you want to calculate the data 
on the fly, and that's where Eryksun's suggestion to use a generator is 
specially useful:


class MyPseudoIterator:
    def __iter__(self):
        # use "yield" instead of "return"
        yield "something"
        yield "another thing"
        result = calculate_stuff()
        yield result


Notice that in both cases I don't declare a __next__ method! That's 
because I never call next() on MyPseudoIterator instances directly, I 
always call iter() first:

# this won't work
instance = MyPseudoIterator()
result = next(instance)  # fails

# this does
instance = MyPseudoIterator()
it = iter(instance)
result = next(it)  # works


For-loops are okay since Python automatically calls iter for you:

instance = MyPseudoIterator()
for item in instance:
    ...


> Second: you state that I don't have to worry about the name
> difference, but when I changed the method name from next to __next__
> it worked in 3.3. So what's your point here?

I think that Eryksun means that in Python 2, you can call:

it.next()

but in Python 3 you can call:

it.__next__()

but neither are recommended, instead you should call:

next(it)

and let the built-in next() function worry about the difference.



-- 
Steven


More information about the Tutor mailing list