Watch Now This tutorial has a related video course created by the Real Python team. Watch it together with the written tutorial to deepen your understanding: The Python range() Function
A range is a Python object that represents an interval of integers. Usually, the numbers are consecutive, but you can also specify that you want to space them out. You can create ranges by calling range() with one, two, or three arguments, as the following examples show:
>>> list(range(5))
[0, 1, 2, 3, 4]
>>> list(range(1, 7))
[1, 2, 3, 4, 5, 6]
>>> list(range(1, 20, 2))
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
In each example, you use list() to explicitly list the individual elements of each range. You’ll study these examples in more detail soon.
In this tutorial, you’ll learn how you can:
- Create
rangeobjects that represent ranges of consecutive integers - Represent ranges of spaced-out numbers with a fixed step
- Decide when
rangeis a good solution for your use case - Avoid
rangein most loops
A range can sometimes be a powerful tool. However, throughout this tutorial, you’ll also explore alternatives that may work better in some situations. You can click the link below to download the code that you’ll see in this tutorial:
Get Your Code: Click here to download the free sample code that shows you how to represent numerical ranges in Python.
Construct Numerical Ranges
In Python, range() is built in. This means that you can always call range() without doing any preparations first. Calling range() constructs a range object that you can put to use. Later, you’ll see practical examples of how to use range objects.
You can provide range() with one, two, or three integer arguments. This corresponds to three different use cases:
- Ranges counting from zero
- Ranges of consecutive numbers
- Ranges stepping over numbers
You’ll learn how to use each of these next.
Count From Zero
When you call range() with one argument, you create a range that counts from zero and up to, but not including, the number you provided:
>>> range(5)
range(0, 5)
Here, you’ve created a range from zero to five. To see the individual elements in the range, you can use list() to convert the range to a list:
>>> list(range(5))
[0, 1, 2, 3, 4]
Inspecting range(5) shows that it contains the numbers zero, one, two, three, and four. Five itself is not a part of the range. One nice property of these ranges is that the argument, 5 in this case, is the same as the number of elements in the range.
Count From Start to Stop
You can call range() with two arguments. The first value will be the start of the range. As before, the range will count up to, but not include, the second value:
>>> range(1, 7)
range(1, 7)
The representation of a range object just shows you the arguments that you provided, so it’s not super helpful in this case. You can use list() to inspect the individual elements:
>>> list(range(1, 7))
[1, 2, 3, 4, 5, 6]
Observe that range(1, 7) starts at one and includes the consecutive numbers up to six. Seven is the limit of the range and isn’t included. You can calculate the number of elements in a range by subtracting the start value from the end value. In this example, there are 7 - 1 = 6 elements.
Count From Start to Stop While Stepping Over Numbers
The final way to construct a range is by providing a third argument that specifies the step between elements in the range. By default, the step is one, but you can pass any non-zero integer. For example, you can represent all odd numbers below twenty as follows:
>>> range(1, 20, 2)
range(1, 20, 2)
Here, you specify that you want a range of numbers from one to twenty that are two apart. Have a look at the numbers that are part of this range:
>>> list(range(1, 20, 2))
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
You can confirm that the range contains all odd numbers below twenty. The difference between consecutive elements in the range is two, which is equal to the step value that you provided as the third argument.
In these examples, you’ve gotten started using ranges in Python. In the rest of the tutorial, you’ll learn more about how range() works under the hood, and you’ll see several examples of when you’d want to use range objects in your own code.
Use Python’s range() Function to Create Specific Ranges
You’ve seen the syntax of range() and how you use one, two, or three arguments to specify different kinds of ranges. In this section, you’ll dig deeper into Python’s range() function and see how to represent specific ranges.
Note: Technically, range() is not a function. Instead, range is a type or class. When you call range(), you’re calling the constructor of the range class to create a new range object.
Still, for all practical purposes, you can treat range() as a function that returns a range object.
First, note that a range is a lazy sequence. This means that Python doesn’t create the individual elements of a range when you create the range. Instead, it creates each element when you ask for it, for example by iterating over the range.
This is different from, say, a list. Lists are eagerly constructed, so that all elements in the list are present in memory immediately when the list is created. That’s why you converted ranges into lists to inspect the individual elements earlier:
>>> range(1, 7)
range(1, 7)
>>> list(range(1, 7))
[1, 2, 3, 4, 5, 6]
The range object is lazy. In this example, the individual numbers in the range are first referenced when you convert it to a list. Additionally, you don’t exhaust ranges when you iterate over them. You can reuse them as many times as you want.
One property of ranges that may seem unintuitive is that the start value of the range is included, but the end value isn’t. Python ranges represent half-open intervals, to use a technical term.
Closed intervals, which include both the start and end values, are more common in daily use. For example, a regular dice has values from one to six. However, using half-open intervals in programming has several advantages,which Edsger W. Dijkstra outlines as follows:
- If the start value isn’t included in the range, then you’d need to use
-1to specify a range running from zero. - If the end value is included in the range, then it’s awkward to define an empty range.
- If the range is defined as a half-open interval, then it’s straightforward to calculate the number of elements in the range.
You’ve already seen a few examples of the latter point. Recall that range(1, 7) contains six elements which you calculate as 7 - 1 = 6. Next, you’ll explore how to create special kinds of ranges.
Handle Ranges Over Negative Numbers
Probably, most of the ranges that you’ll construct will consist of positive numbers. If you only provide one argument when you create a range, then, as you’ve learned, the range counts from zero and up.
However, nothing’s stopping you from using negative numbers in your ranges. For example, you can create the numbers from negative ten to zero:
>>> range(-10, 0)
range(-10, 0)
>>> list(range(-10, 0))
[-10, -9, -8, -7, -6, -5, -4, -3, -2, -1]
Using negative numbers as arguments works similarly to positive numbers. The first element in the range is the start, while the second one is the end, as usual. You can use negative values for both arguments. Here are the numbers from negative seven to negative three:
>>> range(-7, -3)
range(-7, -3)
>>> list(range(-7, -3))
[-7, -6, -5, -4]
As always, the end value isn’t included in the range. You can still calculate the number of elements by looking at the difference of the arguments. Just keep track of the negative signs: (-3) - (-7) = 4.
Work With an Empty Range
You can use any integer as a value for the first two arguments. However, many choices will lead to empty ranges. In particular, if the arguments are equal, then you know that the corresponding range will have zero elements. In other words, it’ll be empty:
>>> range(1, 1)
range(1, 1)
>>> list(range(1, 1))
[]
Here, you construct a range starting at 1. Since 1 is also the end of the range, it’s not included, so the range is empty.
In general, an empty range isn’t particularly useful. You probably won’t create an empty range explicitly. However, if you’re working with ranges with dynamic arguments, then some of those arguments may lead to an empty range.
As you’ve learned, there are many ways you can create an empty range. If you need to create one explicitly, then the clearest is probably one counting from zero to zero:
>>> range(0)
range(0, 0)
>>> list(range(0))
[]
You confirm that range(0) represents an empty range.
If your first argument is larger than the second one, then you’ll typically end up with an empty range as well:
>>> range(4, 2)
range(4, 2)
>>> list(range(4, 2))
[]
Here, you’ve constructed a range that runs from four and up to two. Since four is larger than two, there are no numbers in that range.
Sometimes, you want to count down from a larger number. That’s what you’ll explore next.
Count Backward With Negative Steps
So far, you’ve looked at ranges with positive steps. By default, ranges use a step equal to one, but you can use any integer except zero. The following range contains all even numbers from twenty and down to zero:
>>> range(20, 0, -2)
range(20, 0, -2)
>>> list(range(20, 0, -2))
[20, 18, 16, 14, 12, 10, 8, 6, 4, 2]
By using a negative step, the range counts down from start to stop. As always, the stop isn’t included.
Just as most positive ranges use the default step of one, a step value of negative one is common when counting backward:
>>> range(5, -1, -1)
range(5, -1, -1)
>>> list(range(5, -1, -1))
[5, 4, 3, 2, 1, 0]
This range represents counting down from five to zero, inclusive. If you use a negative step, then the range will be empty if the second argument is larger than or equal to the first.
You’ve seen that you can use any integer value for the arguments, although the last one can’t be zero. In the next section, you’ll look into some practical use cases where you can loop through a range, as well as situations in which an alternative may be more appropriate.
Loop Through Ranges or Use an Alternative
In Python, you can create loops using two different constructs:
- A
whileloop, or an indefinite loop, repeats an operation until a condition is fulfilled. - A
forloop, or a definite loop, repeats an operation for each element in an existing sequence.
Since a range is a sequence, you typically use for loops to iterate over ranges.
In many languages, including C++, Java, and JavaScript, for loops are mainly based on indices. Python loops are different. In Python, a for loop is based on sequence elements instead of indices. If you’re more familiar with a different language, you may need to adjust how you approach loops.
In particular, if you want to re-create an index-based loop, then you may be tempted to use a range:
>>> word = "Loop"
>>> for index in range(len(word)):
... print(index, word[index])
...
0 L
1 o
2 o
3 p
You loop over indices and use each index to pick out the corresponding letter from the word "Loop".
This approach works, but it’s usually not the best way to work with loops in Python. In fact, if you’re treating a range as indices in Python, then you should look for an alternative. There are better ways, which you’ll explore in the upcoming sections. But first, you’ll study a situation in which range() is the right tool for the job.
Repeat an Operation
If you need to repeat an operation a fixed number of times, then using a range is often a good solution. You know that range(n) is a sequence that contains n elements, so looping over such a range will repeat an operation n times.
For example, the following code block repeats the print() calls three times:
>>> for _ in range(3):
... print("Knock, knock, knock")
... print("Penny!")
...
Knock, knock, knock
Penny!
Knock, knock, knock
Penny!
Knock, knock, knock
Penny!
In this code, you repeat the indented block for each element in the range. Because the elements of the range themselves are unimportant, you use the underscore (_) as a throwaway variable.
There are also times when you work with genuine ranges of numbers, and you want to create loops over those ranges. In the next example, you’ll create a multiplication table that shows the products of all combinations of integers up to ten:
>>> for number in range(1, 11):
... for product in range(number, number * 11, number):
... print(f"{product:>4d}", end="")
... print()
...
1 2 3 4 5 6 7 8 9 10
2 4 6 8 10 12 14 16 18 20
3 6 9 12 15 18 21 24 27 30
4 8 12 16 20 24 28 32 36 40
5 10 15 20 25 30 35 40 45 50
6 12 18 24 30 36 42 48 54 60
7 14 21 28 35 42 49 56 63 70
8 16 24 32 40 48 56 64 72 80
9 18 27 36 45 54 63 72 81 90
10 20 30 40 50 60 70 80 90 100
You create two loops that together will set up the two-dimensional multiplication table. First, you loop over the numbers from one up to and including ten. These will represent the rows in the table, and you can see those numbers at the beginning of each row.
You use the second loop to populate each column of a given row. In this range, you use the arguments to calculate number times each integer from one to ten. In particular, the last step argument makes sure that numbers in each row are correctly spaced out.
To format the table, you use an f-string and the end parameter of print(), which keeps each number on the same line. In this example, you’re working directly with the range of numbers, and it makes sense to use the range in a loop.
Loop Directly Over the Iterator Instead
Earlier, you used a range to construct the indices of a string. A small variation of that example gives you the individual characters of the string:
>>> word = "Loop"
>>> for index in range(len(word)):
... print(word[index])
...
L
o
o
p
In most loops, the index isn’t necessary at all. If you’re interested in the characters of a string, then you can take advantage of the string already being iterable. This means that you can loop directly on the string itself:
>>> word = "Loop"
>>> for char in word:
... print(char)
...
L
o
o
p
Looping directly on a sequence, like you do here, is simpler and more readable than using indices. If you have a loop where you’re using indices to find individual elements, then you should loop directly on the elements instead.
Use enumerate() to Create Indices Instead
Sometimes, you want to work with both indices and the corresponding elements. In the earlier example, you showed the index of each character in a word:
>>> word = "Loop"
>>> for index in range(len(word)):
... print(index, word[index])
...
0 L
1 o
2 o
3 p
For use cases like these, you can use enumerate() to create indices instead of range(). With enumerate(), you wrap an iterable and get access to both the index and the element for each item:
>>> word = "Loop"
>>> for index, char in enumerate(word):
... print(index, char)
...
0 L
1 o
2 o
3 p
With enumerate(), you generate an index for each element. You can also customize the counter by passing the start argument to enumerate(). Then, the index will begin counting at start instead of zero, which is the default. For example, you can start counting letters at index one:
>>> word = "Loop"
>>> for index, char in enumerate(word, start=1):
... print(index, char)
...
1 L
2 o
3 o
4 p
Now the first letter is labeled by 1 instead of 0.
The following example is a bit more involved. You have a grid representing a treasure map. The treasures are marked by X. The center of the map is at the coordinates (0, 0), and it’s marked by o. You can use enumerate() to find the coordinates of the treasures as follows:
>>> grid = """
... .............
... .........X...
... ...X..o......
... .............
... ...........X.
... """.strip()
...
... rows = grid.split("\n")
... for row, line in enumerate(rows, start=-(len(rows) // 2)):
... for col, char in enumerate(line, start=-(len(line) // 2)):
... if char == "X":
... print(f"Treasure found at ({row}, {col})")
...
Treasure found at (-1, 3)
Treasure found at (0, -3)
Treasure found at (2, 5)
You first loop over each line in the grid, using enumerate() to access the row coordinates. By setting start=-(len(rows) // 2), you ensure that the middle row is labeled with index 0. In this example, the first row gets row coordinate -2.
Similarly, you loop over each character of each row. Now, you start counting at -(len(line) // 2), which in this example is -6. According to your program, one treasure is at (-1, 3) which translates to one row above the center, three columns to the right of the center. The other treasures are at (0, -3) and (2, 5).
If you need to access both an element and its corresponding index, then you should use