20Sep
artwork depicting a stylized Python range() button
Explaining and visualizing the range(0 function in Python with examples

We’re continuing to delve into the inner workings of various Python functions, modules, and libraries. Having explored enumerate(), we now delve into another essential function — range().

In this article, we’ll explore the functionality of range() and highlight its use cases — and you’ll know how to use range() in Python efficiently, appreciating both its strong sides and limitations. range()-related questions may often pop up during technical interviews — have you checked our Python interview questions yet?

(As always, the code in this article is from Python 3)

So What Does range() Actually Do in Python?

range() is a built-in function, meaning that Python comes prepackaged with it. This function can create a sequence of numbers (this is called a range object) and return it. Naturally, you can utilize this set of numbers for various purposes: as demonstrated below, range() actually accompanies loops very well.

Here’s a more technical explanation provided py the Python help() module:

Return an object that produces a sequence of integers from start (inclusive) to stop (exclusive) by step. range(i, j) produces i, i+1, i+2, …, j-1. start defaults to 0, and stop is omitted! range(4) produces 0, 1, 2, 3. These are exactly the valid indices for a list of 4 elements. When a step is given, it specifies the increment (or decrement).

Syntax of range() in Python

Let’s visualize the most simple scenario: range(5)

Visualization of how range() works in Python
Ranging the sequence…

The syntax of the range() function is trivial — we call the function and input the parameters:

range([start], stop, [step])

Let’s take a closer look at what each parameter does:

  1. start is an optional parameter that defines the starting point of our sequence. When not specified, it defaults to 0.
  2. stop is a required parameter that defines the endpoint of our sequence.
  3. step is an optional parameter that defines the step size (i.e. the number of integers that will be omitted between individual integers in the sequence). When not specified, it defaults to 0.

Combining range() with List() in Python

Visualization of how lists and the range function work in Python
Ranging and listing

We might want to showcase which numbers are actually used in the sequence when the parameter is, say, 5. To do this, we can use another built-in Python function — list — to create a list of numbers that a function call range(5) will produce:

print(list(range(5)))

This will output:

[0, 1, 2, 3, 4]

Combining Range() with for Loops in Python

Visualization of how for loops and the range function work in Python
Looping and ranging

Alternatively, we can utilize a for loop — it will allow us to execute the given command a number of times. This process is called iteration and we can use various data structures (e.g. strings or lists) to specify the exact amount of ”repetition.” Naturally, we can also the range() function for this purpose — it will basically run the command N times.

for i in range(5):
    print(i)

Thanks to Python’s clear syntax, it’s easy to memorize how for loops work like this: ”For every element/part/item in something, do this.” However, the output will be vertical (each print call will end with a newline) — some developers may find it not convenient or readable. To print the output horizontally, we can add the end parameter to our print function:

for i in range(5):
    print(i, end=', ')

This way, the result would be easier to read:

0, 1, 2, 3, 4,

A Note About the ‘Stop’ Parameter of range() (a.k.a. Inclusive Ranges in Python)

At this point, we need to reiterate how indices work in almost every programming language. Since we didn’t specify the start parameter, it defaulted to 0 and our sequence became 0 — 5. You’d think that if we put this sequence in a list and print it, the list would look like this:

[0, 1, 2, 3, 4, 5]

However, the last index (i.e. the stop argument) isn’t included in the operation, so it’s useful to memorize the formula as range(from number X up to — but not including! — number Z).

Let’s examine a more complicated scenario when we want to use all three arguments:

Visualization of how range() parameters work in Python
Ranging and stopping

The code in the previous sections (range(5)) actually received 3 parameters even though we only inputted one.

Creating a Reverse range() Object in Python

So far we’ve only been using positive integers to construct range objects that follow the normal (i.e. ascending) order — but what if we want to use range() in reverse order (i.e. backwards)? To implement this idea, we’ll need to make our third range() parameter — stopnegative. Let’s imagine that it’s December 31st and we’re only 10 seconds away from New Year — a countdown timer will help us, as its name suggests, count these 10 seconds down:

print("The New Year is upon us!")
for i in range(10, 0, -1):
    print(str(i) + '...')
print("Happy New Year!")

This will output:

The New Year is upon us!
10...
9...
8...
7...
6...
5...
4...
3...
2...
1...
Happy New Year!

Note: To concatenate (i.e. combine two pieces of data) integers and ellipses (the ‘…’ symbols) we had to convert integers to string, calling the str() function on them — str(i) in our example. Otherwise, we would run into a TypeError:

TypeError: unsupported operand type(s) for +: 'int' and 'str'

range() in Python: Applying List Operations

Thanks to the fact that range() returns a list, we can apply various list operations to it, including slicing; xrange(), on the other hand, doesn’t offer this functionality.

r = range(10)
sliced_r = r[3:6]
another_sliced_r = r[5:]
yet_another_sliced_r = r[:5]
reversed_r = r[::-1]

print(f"Here's the original range object: {r}")
print(f"Here's a sliced range object: {sliced_r}")
print(f"Here's another sliced range object: {another_sliced_r}")
print(f"Here's yet another sliced range object: {yet_another_sliced_r}")
print(f"Here's a reversed range object: {reversed_r}")

This will output:

Here's the original range object: range(0, 10)
Here's a sliced range object: range(3, 6)
Here's another sliced range object: range(5, 10)
Here's yet another sliced range object: range(0, 5)
Here's a reversed range object: range(9, -1, -1)

range() vs xrange() in Python: What’s the Difference?

Visualization of how range() and xrange() work in Python
Ranging and… x-ranging?

Python 3 brought a number of improvements, but the reasoning behind some of these changes wasn’t so clear. A good example is transition from xrange() (Python 2) to range() (Python 3). New language features are typically better than their predecessors, but xrange() still has the upper hand in some areas. Let’s explore how they compare:

range() vs xrange() in Python: What These Functions Return

Although they share the same functionality, what they return is the main difference.

  • range() returns a list.
  • xrange() returns an xrange object .

Despite the fact that their output is the same, the difference in their return values is an important point to consider — it influences the way these functions perform and the ways they can be used.

range() vs xrange() in Python: How Fast These Functions Perform

Performance is arguably the most important factor when we compare Technology A and Technology B. Both range() and xrange(), of course, are blazingly fast when dealing with smaller sets of data; when it comes to larger numbers, however, the difference in speed becomes apparent. One of the possible explanations for the switch from xrange() to range() is performance — but has performance actually increased?

The built-in Python module timeit is optimal for testing the performance of small code snippets. Let’s use it to compare range() with xrange() (notice that we’re running these commands from the command line, not from the Python interpreter):

# testing range()
python -m timeit 'for i in range(1000000):' ' pass'
10 loops, best of 3: 90.5 msec per loop
# testing xrange()
python -m timeit 'for i in xrange(1000000):' ' pass'
10 loops, best of 3: 51.1 msec per loop

Although these metrics may vary from computer to computer, the average speed difference is around 100%! This is possible thanks to the fact xrange() returns a generator object — it allows to only process the range of numbers that the user demands, saving time and resources. (Fun fact: This process is called ”lazy evaluation”).

range() vs xrange() in Python: How Much Memory These Functions Use

Similar to timeit, another built-in Python module called sys can help us see how much memory these two functions use. To do this, we’ll use the getsizeof() function from the sys module:

import sys

xr = xrange(1, 10000)
r = range(1, 10000)

size_xr = sys.getsizeof(xr)
size_r = sys.getsizeof(r)

print(f"The xrange() function uses {size_xr} bytes of memory.")
print(f"The range() function uses {size_r} bytes of memory.")

This will output:

The xrange() function uses 24 bytes of memory.
The xrange() function uses 80064 of memory.

Conclusion

The number of useful modules, libraries, and functions in Python may seem overwhelming at times. Now, however, you’ve mastered another awesome function — and now range() will always be a loyal companion in your programming sessions. 🙂

One Reply to “Python range() Explained and Visualized”

  1. I love your ‘Python function explained’ series!
    You DID clear my mind… and I really like your beautiful and helpful image.

    If you don’t mind, may I quote some of your images and writings in writing a blog?

    And… I found the little typo on your last code, how to use the memory each function.

    Previous,
    The xrange() function uses 24 bytes of memory.
    The xrange() function uses 80064 of memory.

    It should be,
    The xrange() function uses 24 bytes of memory.
    The range() function uses 80064 of memory.

Leave a Reply