Why is the range object "not an iterator"?
Question:
I wrote this and expected 0
:
>>> x = range(20)
>>> next(x)
Instead I got:
TypeError: ‘range’ object is not an iterator
But I thought it was a generator?
The initial answer yielded the same thing I initially said to myself: it’s an iterable, not an interator. But then, that wouldn’t explain why this works, if both are simply generators:
>>> x = (i for i in range(30))
>>> next(x)
0
Answers:
The range object is iterable. However, it’s not an iterator.
To get an iterator, you need to call iter()
first:
>>> r=range(5,15)
>>> next(iter(r))
5
>>> next(iter(r))
5
>>> next(iter(r))
5
>>> next(iter(r))
5
>>> i=iter(r)
>>> next(i)
5
>>> next(i)
6
>>> next(i)
7
>>> next(i)
8
>>> iter(r)
<range_iterator object at 0x10b0f0630>
>>> iter(r)
<range_iterator object at 0x10b0f0750>
>>> iter(r)
<range_iterator object at 0x10b0f0c30>
Edit: But be careful not to call iter()
with every call to next()
. It creates a new iterator at index 0.
range
returns an iterable, not an iterator. It can make iterators when iteration is necessary. It is not a generator.
A generator expression evaluates to an iterator (and hence an iterable as well).
It’s because the next
function calls the next
method of the object that passed in.
next(...)
x.next() -> the next value, or raise StopIteration
listiterator
s and generator
s both have the next
method.
>>> iter(range(1)).__class__.next
<slot wrapper 'next' of 'listiterator' objects>
>>> iter(x for x in range(1)).__class__.next
<slot wrapper 'next' of 'generator' objects>
But a list
doesn’t have it. And that is the reason why it raises that exception.
>>> list.next
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: type object 'list' has no attribute 'next'
next
doesn’t care much about whether the object it’s passed is an iterator or not.
>>> class Foo():
... def next(self):
... return "foo"
...
>>> foo = Foo()
>>> next(foo)
'foo'
>>> next(foo)
'foo'
But adding the next
method doesn’t necessarily make it a collection/sequence/iterable.
>>> class Foo():
... def next(self):
... return "Foo"
>>> [x for x in Foo()]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: iteration over non-sequence
>>> iter(Foo())
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: iteration over non-sequence
But adding the __iter__
method to it makes it one.
>>> class Foo():
... def next(self):
... return "Foo"
... def __iter__(self): return self
...
>>> [x for x in Foo()]
^CTraceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyboardInterrupt
>>> iter(Foo())
<__main__.Foo instance at 0x7fd77307c488>
The next
seems to have some builtin intelligence when it comes to list
.
>>> class Foo():
... pass
...
>>> foo = Foo()
>>> next(foo)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: instance has no next() method
>>> next(range(20))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: list object is not an iterator
The next
builtin calls the __next__
hook method. So, range
objects have a well defined __iter__
, but not a well-defined __next__
.
iterable objects have __iter__
defined, iterator objects have well defined __next__
(typically with an __iter__
method which simply returns self
).
I wrote this and expected 0
:
>>> x = range(20)
>>> next(x)
Instead I got:
TypeError: ‘range’ object is not an iterator
But I thought it was a generator?
The initial answer yielded the same thing I initially said to myself: it’s an iterable, not an interator. But then, that wouldn’t explain why this works, if both are simply generators:
>>> x = (i for i in range(30))
>>> next(x)
0
The range object is iterable. However, it’s not an iterator.
To get an iterator, you need to call iter()
first:
>>> r=range(5,15)
>>> next(iter(r))
5
>>> next(iter(r))
5
>>> next(iter(r))
5
>>> next(iter(r))
5
>>> i=iter(r)
>>> next(i)
5
>>> next(i)
6
>>> next(i)
7
>>> next(i)
8
>>> iter(r)
<range_iterator object at 0x10b0f0630>
>>> iter(r)
<range_iterator object at 0x10b0f0750>
>>> iter(r)
<range_iterator object at 0x10b0f0c30>
Edit: But be careful not to call iter()
with every call to next()
. It creates a new iterator at index 0.
range
returns an iterable, not an iterator. It can make iterators when iteration is necessary. It is not a generator.
A generator expression evaluates to an iterator (and hence an iterable as well).
It’s because the next
function calls the next
method of the object that passed in.
next(...)
x.next() -> the next value, or raise StopIteration
listiterator
s and generator
s both have the next
method.
>>> iter(range(1)).__class__.next
<slot wrapper 'next' of 'listiterator' objects>
>>> iter(x for x in range(1)).__class__.next
<slot wrapper 'next' of 'generator' objects>
But a list
doesn’t have it. And that is the reason why it raises that exception.
>>> list.next
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: type object 'list' has no attribute 'next'
next
doesn’t care much about whether the object it’s passed is an iterator or not.
>>> class Foo():
... def next(self):
... return "foo"
...
>>> foo = Foo()
>>> next(foo)
'foo'
>>> next(foo)
'foo'
But adding the next
method doesn’t necessarily make it a collection/sequence/iterable.
>>> class Foo():
... def next(self):
... return "Foo"
>>> [x for x in Foo()]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: iteration over non-sequence
>>> iter(Foo())
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: iteration over non-sequence
But adding the __iter__
method to it makes it one.
>>> class Foo():
... def next(self):
... return "Foo"
... def __iter__(self): return self
...
>>> [x for x in Foo()]
^CTraceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyboardInterrupt
>>> iter(Foo())
<__main__.Foo instance at 0x7fd77307c488>
The next
seems to have some builtin intelligence when it comes to list
.
>>> class Foo():
... pass
...
>>> foo = Foo()
>>> next(foo)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: instance has no next() method
>>> next(range(20))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: list object is not an iterator
The next
builtin calls the __next__
hook method. So, range
objects have a well defined __iter__
, but not a well-defined __next__
.
iterable objects have __iter__
defined, iterator objects have well defined __next__
(typically with an __iter__
method which simply returns self
).