[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