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)
In [5]:
for n in (10, 1000, 10**8):
f = xrange
assert foo(f, n) == get_known_good_output(n)
%timeit foo(f, n)
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]:
In [8]:
f = range
assert foo(f, n, last) == known_good_output
%timeit foo(f, n, last)
In [9]:
f = xrange
assert foo(f, n, last) == known_good_output
%timeit foo(f, n, last)
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().