32

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
demongolem
  • 9,474
  • 36
  • 90
  • 105
temporary_user_name
  • 35,956
  • 47
  • 141
  • 220

4 Answers4

32

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.

Bruno Bronosky
  • 66,273
  • 12
  • 162
  • 149
NPE
  • 486,780
  • 108
  • 951
  • 1,012
13

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).

Mike Graham
  • 73,987
  • 14
  • 101
  • 130
  • 2
    Note that you get an iterator out of this iterable by calling iter() on it. You can then call the next() builtin on the result from iter(range(...)). It's confusing but just one of those things you learn when you dive deeply enough into the language. Note that for loops implicitly call iter(expression) once and then next(results_from_iter) for every pass through the loop. – Jim Dennis Jan 16 '18 at 06:33
6

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).

mgilson
  • 300,191
  • 65
  • 633
  • 696
3

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

listiterators and generators 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
Bleeding Fingers
  • 6,993
  • 7
  • 46
  • 74