Sign in to your Python Morsels account to save your screencast settings.
Don't have an account yet? Sign up here.
Python's range
objects are lazy sequences.
range
objects don't store their valuesIf you make a very large range
, Python will make the range
object very quickly.
>>> numbers = range(10**12)
>>> numbers
range(0, 1000000000000)
Unlike a list or a tuple, range
objects don't actually store the values that they contain.
They only compute those values just before we need them.
range
answers questions lazilySo if we ask this range
object for its last number, it computes that answer very quickly:
>>> numbers[-1]
999999999999
It also computes its length when we ask it for its length:
>>> len(numbers)
And if we were to loop over it, it computes each item as we loop:
>>> for n in numbers:
... print(n)
... if n > 3:
... break
...
0
1
2
3
4
range
objects iterators?If you're familiar with iterables and iterators in Python, you might think that range
objects seem like iterators.
And range
objects do seem like iterators, but they're not iterators.
An iterator will compute its next item as you loop over it:
>>> colors = ["purple", "blue", "green", "orange"]
>>> color_iterator = iter(colors)
>>> for color in color_iterator:
... print(color)
...
purple
blue
green
orange
And so will range
objects:
>>> numbers = range(5)
>>> for n in numbers:
... print(n)
...
0
1
2
3
4
But an iterator's items are consumed as you loop over it, so this iterator is actually empty now:
>>> for color in color_iterator:
... print(color)
...
But unlike that iterator, our range
object isn't empty:
>>> for n in numbers:
... print(n)
...
0
1
2
3
4
That range
object hasn't changed.
It works exactly the same way as it did the first time we looped over it.
range
objects don't get consumedI often refer to iterators as lazy iterables.
While range
objects are lazy, they're actually more sophisticated than iterators.
You can think of them as lazy sequences.
If we ask a range
object for an iterator (in order to loop over it manually) it will give us a new iterator object back:
>>> numbers = range(5)
>>> i1 = iter(numbers)
>>> next(i1)
0
If we ask it for another iterator object, it gives us a second iterator object that's independent of the first iterator:
>>> i2 = iter(numbers)
>>> next(i2)
0
>>> next(i2)
1
If we did the same thing with an iterator, we would get back the original iterator object:
>>> colors = ["purple", "blue", "green", "orange"]
>>> color_iterator = iter(colors)
>>> iterator_again = iter(color_iterator)
>>> color_iterator
<list_iterator object at 0x7f9f6ef70be0>
>>> iterator_again
<list_iterator object at 0x7f9f6ef70be0>
Unlike an iterator, a range
object doesn't change its state.
Instead, range
objects generate a new iterator for us every time we loop over them.
Now, you might ask, does it matter that range
objects aren't iterators?
It doesn't matter if we're just trying to convey that they're lazy, but it would be less confusing to just refer to them as "lazy".
If you told me that something was an iterator, I can make some assumptions about that object.
If we start to loop over an iterator, and then we stop, that iterator will be partially consumed:
>>> colors = ["purple", "blue", "green", "orange"]
>>> color_iterator = iter(colors)
>>> for color in color_iterator:
... print(color)
... break
...
purple
So when we start looping over it again, it'll start up where it left off:
>>> for color in color_iterator:
... print(color)
...
blue
green
orange
That's true of any iterator.
But it's not true of range
objects; range
objects do the same thing every time we loop over them.
Also, iterators all work with the built-in next
function to get their next item:
>>> colors = ["purple", "blue", "green", "orange"]
>>> color_iterator = iter(colors)
>>> next(color_iterator)
'purple'
But range
objects don't, because range
objects are not iterators:
>>> numbers = range(10)
>>> next(numbers)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'range' object is not an iterator
range
objects are lazy sequencesPython's range
objects are not iterators.
Even though range
objects are not iterators, they are lazy, meaning they don't actually store their data, they compute their data as you loop over them and as you index them.
You can think of range
objects as lazy sequences.
Need to fill-in gaps in your Python skills?
Sign up for my Python newsletter where I share one of my favorite Python tips every week.
Unlike, JavaScript, C, Java, and many other programming languages we don't have traditional C-style for
loops.
Our for
loops in Python don't have indexes.
This small distinction makes for some big differences in the way we loop in Python.
To track your progress on this Python Morsels topic trail, sign in or sign up.
Sign in to your Python Morsels account to track your progress.
Don't have an account yet? Sign up here.