Python's range() Function (Guide) β Real Python
Python’s built-in range
function is handy when you need to perform an action a specific number of times.
By the end of this article, you’ll:
- Understand how Python’s
range
function works - Know how the implementations differ in Python 2 and Python 3
- Have seen a number of hands-on
range()
examples - Be equipped to work around some of its limitations
Let’s get cracking!
Free Bonus: Click here to get our free Python Cheat Sheet that shows you the basics of Python 3, like working with data types, dictionaries, lists, and Python functions.
The History of range()
Although range()
in Python 2 and range()
in Python 3 may share a name, they are entirely different animals. In fact, range()
xrange
in Python 2.
Originally, both range()
and xrange()
produced numbers that could be iterated over with for-loops, but the former generated a list of those numbers all at once while the latter produced numbers lazily, meaning numbers were returned one at a time as they were needed.
Having huge lists hang around takes up memory, so it’s no surprise that xrange()
replaced range()
, name and all. You can read more about this decision and the xrange()
vs range()
background in PEP 3100.
Note: PEP stands for Python Enhancement Proposal. PEPs are documents that can cover a wide range of topics, including proposed new features, style, governance, and philosophy.
There are a ton of them. PEP 1 explains how they work and is a great place to start.
For the rest of this article, you’ll be using the function as it exists in Python 3.
Here we go!
Let’s Loop
Before we dive into seeing how range()
works, we need to take a look at how looping works. Looping is a key computer science concept. If you want to be a good programmer, mastering loops is among the first steps you need to take.
Here’s an example of a for-loop in Python:
captains = ['Janeway', 'Picard', 'Sisko'] for captain in captains: print(captain)
The output looks like this:
Janeway Picard Sisko
As you can see, a for-loop enables you to execute a specific block of code however many times you want. In this case, we looped through a list of captains and printed each of their names.
Although Star Trek is great and everything, you may want to do more than simply loop through a list of captains. Sometimes, you just want to execute a block of code a specific number of times. Loops can help you do that!
Try the following code with numbers that are divisible by three:
numbers_divisible_by_three = [3, 6, 9, 12, 15] for num in numbers_divisible_by_three: quotient = num / 3 print(f"{num} divided by 3 is {int(quotient)}.")
The output of that loop will look like this:
3 divided by 3 is 1. 6 divided by 3 is 2. 9 divided by 3 is 3. 12 divided by 3 is 4. 15 divided by 3 is 5.
That’s the output we wanted, so the loop got the job done adequately, but there is another way to get the same result by using range()
.
Now that you’re more familiar with loops, let’s see how you can use range()
to simplify your life.
Getting Started With range()
So how does Python’s range
function work? In simple terms, range()
allows you to generate a series of numbers within a given range. Depending on how many arguments you pass to the function, you can decide where that series of numbers will begin and end as well as how big the difference will be between one number and the next.
Here’s a sneak peek of range()
in action:
for i in range(3, 16, 3): quotient = i / 3 print(f"{i} divided by 3 is {int(quotient)}.")
In this for-loop, you were able to simply create a range of numbers that are divisible by 3
, so you didn’t have to provide each of them yourself.
Note: While this example shows an appropriate use of range()
, it’s usually frowned upon to use range()
too often in for-loops.
For example, the following use of range()
would generally be considered not Pythonic:
captains = ['Janeway', 'Picard', 'Sisko'] for i in range(len(captains)): print(captains[i])
range()
is great for creating iterables of numbers, but it’s not the best choice when you need to iterate over data that could be looped over with the in
operator.
There are three ways you can call range()
:
range(stop)
takes one argument.range(start, stop)
takes two arguments.range(start, stop, step)
takes three arguments.
range(stop)
When you call range()
with one argument, you will get a series of numbers that starts at 0
and includes every whole number up to, but not including, the number you have provided as the stop
.
Here’s what that looks like in practice:
for i in range(3): print(i)
The output of your loop will look like this:
0 1 2
That checks out: we have all the whole numbers from 0
up to but not including 3
, the number you provided as the stop
.
range(start, stop)
When you call range()
with two arguments, you get to decide not only where the series of numbers stops but also where it starts, so you don’t have to start at 0
all the time. You can use range()
to generate a series of numbers from A to B using a range(A, B)
. Let’s find out how to generate a range starting at 1
.
Try calling range()
with two arguments:
for i in range(1, 8): print(i)
Your output will look like this:
1 2 3 4 5 6 7
So far, so good: you have all the whole numbers from 1
(the number you provided as the start
) up to but not including 8
(the number you provided as the stop
).
But if you add one more argument, then you’ll be able to reproduce the output you got earlier when you were using the list named numbers_divisible_by_three
.
range(start, stop, step)
When you call range()
with three arguments, you can choose not only where the series of numbers will start and stop but also how big the difference will be between one number and the next. If you don’t provide a step
, then range()
will automatically behave as if the step
is 1
.
Note: step
can be a positive number or a negative number, but it can’t be 0
:
>>> range(1, 4, 0) Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: range() arg 3 must not be zero
If you try to use 0
as your step, then you’ll get an error.
Now that you know how to use step
, you can finally revisit that loop we saw earlier with division by 3
.
Try it for yourself:
for i in range(3, 16, 3): quotient = i / 3 print(f"{i} divided by 3 is {int(quotient)}.")
Your output will look exactly like the output of the for-loop you saw earlier in this article, when you were using the list named numbers_divisible_by_three
:
3 divided by 3 is 1. 6 divided by 3 is 2. 9 divided by 3 is 3. 12 divided by 3 is 4. 15 divided by 3 is 5.
As you see in this example, you can use the step
argument to increase towards a higher number. That’s called incrementing.
Incrementing With range()
If you want to increment, then you need step
to be a positive number. To get an idea of what this means in practice, type in the following code:
for i in range(3, 100, 25): print(i)
If your step
is 25
, then the output of your loop will look like this:
3 28 53 78
You got a range of numbers that were each greater than the preceding number by 25
, the step
you provided.
Now that you’ve seen how you can step forwards through a range, it’s time to see how you can step backwards.
Decrementing With range()
If your step
is positive, then you move through a series of increasing numbers and are incrementing. If your step
is negative, then you move through a series of decreasing numbers and are decrementing. This allows you to go through the numbers backwards.
In the following example, your step
is -2
. That means that you’ll be decrementing by 2
for each loop:
for i in range(10, -6, -2): print(i)
The output of your decrementing loop will look like this:
10 8 6 4 2 0 -2 -4
You got a range of numbers that were each smaller than the preceding number by 2
, the absolute value of the step
you provided.
The most Pythonic way to create a range that decrements is to use range(start, stop, step)
. But Python does have a built-in reversed
function. If you wrap range()
inside reversed()
, then you can print the integers in reverse order.
Give this a try:
for i in reversed(range(5)): print(i)
You’ll get this:
4 3 2 1 0
range()
makes it possible to iterate over a decrementing sequence of numbers, whereas reversed()
is generally used to loop over a sequence in reverse order.
Note: reversed()
also works with strings. You can learn more about the functionality of reversed()
with strings in How to Reverse a String in Python.
Going Deeper With range()
Now that you know the basics of how to use range()
, it’s time to dig a little deeper.
range()
is mainly used for two purposes:
- Executing the body of a for-loop a specific number of times
- Creating more efficient iterables of integers than can be done using lists or tuples
The first use is probably the most common, and you could make the case that itertools gives you a more efficient way to construct iterables than range()
does.
Here are a few more points to keep in mind when you use range.
range()
is a type in Python:
>>> type(range(3)) <class 'range'>
You can access items in a range()
by index, just as you would with a list:
>>> range(3)[1] 1 >>> range(3)[2] 2
You can even use slicing notation on a range()
, but the output in a REPL may seem a little strange at first:
>>> range(6)[2:5] range(2, 5)
Although that output may look odd, slicing a range()
just returns another range()
.
The fact that you can access elements of a range()
by index and slice a range()
highlights an important fact: range()
is lazy, unlike a list, but isn’t an iterator.
Floats and range()
You may have noticed that all of the numbers we have been dealing with so far have been whole numbers, which are also called integers. That’s because range()
can take only integers as arguments.
A Word on Floats
In Python, if a number is not a whole number, then it is a float. There are some differences between integers and floats.
An integer (int
data type):
- Is a whole number
- Does not include a decimal point
- Can be positive, negative, or
0
A floating point number (float
data type):
- Can be any number that includes a decimal point
- Can be positive or negative
Try calling range()
with a float and see what happens:
for i in range(3.3): print(i)
You should get the following error message:
Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'float' object cannot be interpreted as an integer
If you need to find a workaround that will allow you to use floats, then you can use NumPy.
Using NumPy
NumPy is a third-party Python library. If you are going to use NumPy, your first step is to check if you have it installed.
Here’s how you can do that in your REPL:
>>>>>> import numpy
If you get a ModuleNotFoundError
, then you need to install it. To do so, go to your command line and enter pip install numpy
.
Once you have it installed, put in the following:
import numpy as np np.arange(0.3, 1.6, 0.3)
It will return this:
array([0.3, 0.6, 0.9, 1.2, 1.5])
If you want to print each number on its own line, you can do the following:
import numpy as np for i in np.arange(0.3, 1.6, 0.3): print(i)
This is the output:
0.3 0.6 0.8999999999999999 1.2 1.5
Where did 0.8999999999999999
come from?
Computers have trouble saving decimal floating-point numbers in binary floating-point numbers. This leads to all sorts of unexpected representations of numbers.
Note: To learn more about why there are issues representing decimals, you can check out this article and the Python docs.
You might also want to take a look at the decimal library, which is a bit of a downgrade in terms of performance and readability but allows you to represent decimal numbers exactly.
Another option is to use round()
, which you can read more about in How to Round Numbers in Python. Keep in mind that round()
has its own quirks that might generate some surprising results!
Whether or not these floating point errors are an issue for you depends on the problem you’re solving. The errors are going to be in something like the 16th decimal place, which is insignificant most of the time. They are so small that, unless you’re working on calculating satellite orbital trajectories or something, you don’t need to worry about it.
Alternatively, you could also use np.linspace()
. It does essentially the same thing but uses different parameters. With np.linspace()
, you specify start
and end
(both inclusive) as well as the length of the array (instead of step
).
For instance, np.linspace(1, 4, 20)
gives 20 equally spaced numbers: 1.0, ..., 4.0
. On the other hand, np.linspace(0, 0.5, 51)
gives 0.00, 0.01, 0.02, 0.03, ..., 0.49, 0.50
.
Go Forth and Loop
You now understand how to use range()
and work around its limitations. You also have an idea of how this important function has evolved between Python 2 and Python 3.
The next time you need to perform an action a specific number of times, you’ll be all set to loop your heart out!
Happy Pythoning!