128

Perhaps I've fallen victim to misinformation on the web, but I think it's more likely just that I've misunderstood something. Based on what I've learned so far, range() is a generator, and generators can be used as iterators. However, this code:

myrange = range(10)
print(next(myrange))

gives me this error:

TypeError: 'range' object is not an iterator

What am I missing here? I was expecting this to print 0, and to advance to the next value in myrange. I'm new to Python, so please accept my apologies for the rather basic question, but I couldn't find a good explanation anywhere else.

kmario23
  • 57,311
  • 13
  • 161
  • 150
Jeff
  • 2,149
  • 3
  • 17
  • 19
  • 3
    See http://stackoverflow.com/q/13054057/395760 for the distinction between iterators and things which you can iterate over in a `for` loop. –  Oct 26 '12 at 18:26
  • 1
    Would it be correct to say that generators are iterables, but not iterators? – Jeff Oct 26 '12 at 18:37
  • 5
    @Jeff Iterables are objects that `iter` can be used on to obtain an iterator. Iterators are objects that can be iterated through using `next`. Generators is a category of iterators (generator functions and generator expressions). At least that's what I think... – Oleh Prypin Oct 26 '12 at 18:39

1 Answers1

165

range is a class of immutable iterable objects. Their iteration behavior can be compared to lists: you can't call next directly on them; you have to get an iterator by using iter.

So no, range is not a generator.

You may be thinking, "why didn't they make it an iterator"? Well, ranges have some useful properties that wouldn't be possible that way:

  • They are immutable, so they can be used as dictionary keys.
  • They have the start, stop and step attributes (since Python 3.3), count and index methods and they support in, len and __getitem__ operations.
  • You can iterate over the same range multiple times.

>>> myrange = range(1, 21, 2)
>>> myrange.start
1
>>> myrange.step
2
>>> myrange.index(17)
8
>>> myrange.index(18)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: 18 is not in range
>>> it = iter(myrange)
>>> it
<range_iterator object at 0x7f504a9be960>
>>> next(it)
1
>>> next(it)
3
>>> next(it)
5
user2357112
  • 260,549
  • 28
  • 431
  • 505
Oleh Prypin
  • 33,184
  • 10
  • 89
  • 99
  • 17
    Another nice feature of `range` objects is that they have a `__contains__` method which can be used to test whether a value is in a range: `5 in range(10) => True` – kindall Oct 26 '12 at 19:21
  • 1
    Thanks for the answer; this makes sense now. The only thing I want to clear up before accepting your answer is the note in italics about a third of the way down [this](http://wiki.python.org/moin/Generators) page, that states that "in Python 3 range() is a generator". Is this simply incorrect? – Jeff Oct 26 '12 at 19:41
  • 4
    @Jeff Strictly speaking, yes, it is wrong. The author of the note probably meant that in Python 3 `range` is *lazy* (compared to Python 2 where it's just a function that returns a list). – Oleh Prypin Oct 26 '12 at 19:49
  • 7
    Also: `range(0,10,3)[3]` and `9 in range(0,10,3)`. Range is pretty much a lazy list. – Lennart Regebro Oct 27 '12 at 08:44
  • One of the final puzzle pieces falls into place...great answer. – temporary_user_name Feb 15 '14 at 21:34
  • 1
    What is the link between being immutable and not being directly iterable? Even an iterable object seems immutable to me so what exactly is the benefit of or reason behind a range object not being directly iterable? – user2399453 Aug 29 '17 at 04:30
  • 4
    @user3079275 "directly iterable" is a misnomer, actually meaning "iterator". Iterators have internal state and so are mutable by definition. "Iterable" is an object, whether it is mutable or not, that can produce an iterator. Even mutable objects aren't usually iterators themselves, instead they produce iterators in a reusable manner (for example, you can iterate over the same list in two different places independently, using two iterators). – Oleh Prypin Aug 29 '17 at 08:39