[Tutor] range as a not-quite-iterator

Steven D'Aprano steve at pearwood.info
Sun Aug 4 04:03:06 CEST 2013


On 04/08/13 11:14, Jim Mooney wrote:
> using py3.3 on win7
>
> I'm reading the Lutz book, and he clearly calls range an iterator in
> the context of Python 3.0, yet when I try
>
> x = range(1,10)
> next(x)
> I get: builtins.TypeError: 'range' object is not an iterator
> And x itself is returned as: range(1, 10)
>
> What am I missing here? Is it an iterator or not?

Technically, no. Informally, yes.


Technically, there are three rules for something to be an iterator:

* it must have an __iter__ method which returns itself;

* it must have a __next__ method which returns each subsequent value;

* if there are no more values, it must raise StopIteration

(this is called the "iterator protocol").

In addition, well-behaved iterators must continue to always return StopIteration once they have become empty. An iterator that becomes empty, then becomes non-empty, is officially deemed to be "broken", although you are permitted to write broken iterators if you insist.


So by this definition, range (or xrange in Python 2.x) is not an iterator, since it doesn't follow the iterator protocol. However, it does behave like an iterator, in the sense that it is a lazily generated sequence, so informally, it sometimes gets called one. If it quacks like an iterator, you can treat it as an iterator.

Note that range objects have an __iter__ which doesn't return the range object itself, but an iterator wrapper around itself:


py> r = range(2, 45, 3)
py> it = iter(r)  # calls __iter__ internally
py> it is r
False
py> it
<range_iterator object at 0xb7bca320>
py> r
range(2, 45, 3)


That wrapper object actually is a genuine iterator:

py> iter(it) is it
True



The reason for this is partly historical (xrange goes all the way back to Python 1.4 or even older, while iterators only go back to about 2.2, if I remember correctly) and partly practical -- iterators can normally be used only once, while range objects can be re-used, and also can be sliced almost like a list.

py> r[1:3]
range(5, 11, 3)



-- 
Steven


More information about the Tutor mailing list