Difference Between range and xrange in Python

The range() and xrange() functions are used to create iterables or generators of iterable. The two functions have the same arguments, but they are fundamentally different in other ways.

Compatibility with Different Python Versions

The range() function is available in Python 2 and Python 3, but xrange() is only available in Python 2.

Return Type and the Basic Structure

The return type and the basic structure of the objects generated by the two functions depend on your Python version.

In Python 2:

  • range() creates a list. That means if you do range(1, 100), the function returns a list of 99 elements, that is, [1, 2, 3, …, 99]
  • xrange() returns a generator object. That means xrange does not create the list when it is called; instead, it returns an object that evaluates lazily.

Definition (Lazy evaluation): An object is said to be lazily evaluated if each element is only evaluated on demand rather than on initialization.

Let’s see that on code (remember to run this code on Python 2).

# Initializing the range and xrange functions
range_obj0 = range(5)
xrange_obj0 = xrange(5)
# Showing the data types of the returned values
# for the two functions and
# printing the structure of the returned values
print("range data type: ", type(range_obj0))
print("xrange data type: ", type(xrange_obj0))
print("range object 0 of 5 elements: ", range_obj0)
print("range object 0 of possible 5 elements: ",xrange_obj0)

Output:

('range data type: ', <type 'list'>)
('xrange data type: ', <type 'xrange'>)
('range object 0 of 5 elements: ', [0, 1, 2, 3, 4])
('range object 0 of possible 5 elements: ', xrange(5))

From the output, it is apparent that the range() function returned a list and xrange() returned an xrange generator object.

In Python 3:

  • range() function is equivalent to Python 2’s xrange(). The function returns a generator object that can be converted into a list explicitly using list(range_object).
  • xrange() does not exist in Python 3.

Let’s run the following code in Python 3 to see the difference between range() and xrange().

# Initializing range object
range_object = range(10)
# Printing the return type for the function, and,
# casting range object into a list.
print("range object in Python 3: ", range_object)
print("Type of range object in Python: ", type(range_object))
print("Cast range object into a list: ", list(range_object))
# Attempt to use the xrange function in Python 3
xrange(10)

Output:

range object in Python 3:  range(0, 10)
Type of range object in Python:  <class 'range'>
Cast range object into a list:  [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
NameError: name 'xrange' is not defined

Clearly, the output shows that the range() function returns a generator that can be converted into a list using the list() function in Python 3. The result also indicates that xrange() is not available in Python 3.

Memory usage

The amount of memory used by the range() and xrange() function depends on their return types. The function that creates a list in memory will take more space than a function that returns a generator object. That is to say,

  • Python 2: The xrange() generator object takes less space than the range() list.
  • Python 3: The range() generator takes less space than the list generated by list(range_object).

The amount of memory used up by Python 2’s range() and Python 3’s list(range_object) depends on the size of the list (the choice of start, stop, and step parameters in the function).

Using some code, let’s compare the memory usage for range() and xrange() in Python 2.

# Code to run on Python 2.
import sys
# Initialize a list of 5 elements with range().
range_obj0 = range(5)
# Initialize a generator of 5 elements with range().
xrange_obj0 = xrange(5)
# 500-elements list and generator, respectively.
range_obj1 = range(500)
xrange_obj1 = xrange(500)
# Get the amount of memory used by each object.
range_size0 = sys.getsizeof(range_obj0)
xrange_size0= sys.getsizeof(xrange_obj0)
print("size of range_obj0 of 5 elements", range_size0)
print("size of xrange_obj0 generator of 5 elements", xrange_size0)
range_size1 = sys.getsizeof(range_obj1)
xrange_size1= sys.getsizeof(xrange_obj1)
print("size of range_obj1 of 500 elements", range_size1)
print("size of xrange_obj1 of 500  elements", xrange_size1)

Output:

('size of range_obj0 of 5 elements', 112)
('size of xrange_obj0 generator of 5 elements', 40)
('size of range_obj1 of 500 elements', 4072)
('size of xrange_obj1 of 500  elements', 40)

From the output, notice that the generator object takes the same memory space (40 bytes) irrespective of “the size of the object.”

On the other hand, the range() object writes the elements of the returned list into memory, taking more space. The amount of space depends on the size of the list generated. In the example above, the 5-element list takes up 112 bytes, but the 500-element list is allotted 4072 bytes of memory.

The following code in Python 3 compares range() to list(range_object) based on memory usage.

import sys
range_object0 = range(5)
print("Size of 5-element range object in Python3: ", sys.getsizeof(range_object0))
range_list0 = list(range(5))
print("range object cast to list in Python 3: ", range_list0)
print("Size of range object cast to list: ", sys.getsizeof(range_list0))
range_object1 = range(500)
print("Size of 500-element range object in Python3: ", sys.getsizeof(range_object1))
range_list1 = list(range(500))
print("Size of range object cast to list: ", sys.getsizeof(range_list1))

Output:

Size of 5-element range object in Python3:  48
range object cast to list in Python 3:  [0, 1, 2, 3, 4]
Size of range object cast to list:  96
Size of 500-element range object in Python3:  48
Size of range object cast to list:  4056

Just like in Python 2, the range() object in Python 3 takes up the same space (48 bytes) irrespective of the “size of the range object.” On the other hand, if the range object is converted to a list, the result takes more space, and the amount of memory space taken depends on the size of the list.

Operations Usage

All operations applied to Python lists can also be used to range() in Python 2 and list(range_object) in Python 3.

This is because these two methods return Python lists. On the other hand, Python 2’s xrange() and Python 3’s range() may not support most of the list operations. Here are some examples.

range_object0 = range(5)
print(len(range_object0)) # prints 5 for both Python 2 and Python 3.
print(range_object0[0:2]) # Output: [0, 1] in Python 2 and range(0, 2) in Python 3

Operations like append, insert, and remove fail when working with generators.

Speed

In Python 2, evaluation of xrange() object is faster than range(). This is because the xrange object is only a generator required for lazy evaluation. Let’s run the following tests on the command line or terminal to ascertain that.

python2.7 -m timeit 'for i in range(1000000):' ' pass'

Output:

10 loops, best of 3: 23.5 msec per loop
python2.7 -m timeit 'for i in xrange(1000000):' ' pass'

Output:

10 loops, best of 3: 8.25 msec per loop

In Python 3, it is fast to loop on a range object than its equivalent list. Let’s make some runs to test that ascension.

python3.9 -m timeit 'for i in range(1000000):' ' pass'

Output:

10 loops, best of 5: 12.1 msec per loop
python3.9 -m timeit 'for i in list(range(1000000)):' ' pass'

Output:

10 loops, best of 5: 24.8 msec per loop

From the output, notice that working Python 2’s xrange is faster than Python 3’s equivalent range object.

Summary of the differences

The following table summarizes the differences we discussed between range() and xrange() functions.

Metrics

range in Python 2 and Python 3

xrange in Python 2

Compatibility

The function is available in Python 2 and Python 3.

Only available in Python 2

Return types

Returns a list in Python 2 and a generator object in Python 3.

Return generator object.

Memory usage

Python 2’s range takes more memory because it saves the returned list in memory. The amount of space taken depends on the size of the list.

xrange takes a constant memory size of 40 bytes (a tiny memory space) irrespective of possible elements the object can generate.

Operations

All list operations can be applied to range in Python 2 because it returns a list, but Python 3’s range may not support most list operations.

Most list operations may not be applied to the xrange objects.

Speed

range in Python 3 is faster than the range in Python 2 because the latter returns an actual list, but the former returns a generator that can be lazily evaluated.

xrange has a faster evaluation than the range in Python 2 because xrange returns a generator, and range returns a list.