In Python 2, range() returns a list and xrange() returns a generator. I expect generating and iterating through a list to be faster than iterating through a generator. It did not work out that way in the following exercises. xrange() was always faster.


In [1]:
from __future__ import print_function

In [2]:
def get_known_good_output(n):
    n -= 1
    return n * (n+1) // 2

In [3]:
def foo(f, n):
    return sum(f(n))

In [4]:
for n in (10, 1000, 10**8):
    f = range
    assert foo(f, n) == get_known_good_output(n)
    %timeit foo(f, n)


The slowest run took 5.28 times longer than the fastest. This could mean that an intermediate result is being cached 
1000000 loops, best of 3: 723 ns per loop
100000 loops, best of 3: 20.6 µs per loop
1 loops, best of 3: 12.1 s per loop

In [5]:
for n in (10, 1000, 10**8):
    f = xrange
    assert foo(f, n) == get_known_good_output(n)
    %timeit foo(f, n)


The slowest run took 11.68 times longer than the fastest. This could mean that an intermediate result is being cached 
1000000 loops, best of 3: 592 ns per loop
100000 loops, best of 3: 14.3 µs per loop
1 loops, best of 3: 9.56 s per loop

range() makes the whole list before execution can continue, whereas values from xrange() a generated one at a time, so range() requires enough memory to hold the entire list in memory whereas xrange() only needs a little bit of memory. For large values, range will use up all memory then crash whereas xrange will just work. Also, since range() makes the whole list before continuing, range() has greater latency. The following cells demonstrate that.


In [6]:
def foo(f, n, last):
    total = 0
    for i in f(n):
        total += i
        if i >= last:
            break
    return total

In [7]:
n = 10**8
last = 100
known_good_output = get_known_good_output(last+1)
known_good_output


Out[7]:
5050

In [8]:
f = range
assert foo(f, n, last) == known_good_output
%timeit foo(f, n, last)


1 loops, best of 3: 2.45 s per loop

In [9]:
f = xrange
assert foo(f, n, last) == known_good_output
%timeit foo(f, n, last)


100000 loops, best of 3: 7.51 µs per loop

For Python 2, I prefer the behavior of xrange() over that of range(). xrange() has low latency for the first value and is thrifty with memory.

However, I dislike the ugly x in the name of xrange and usually stick to range() for portability with Python 3 unless I really need the behavior of xrange().