Why is range(0) == range(2, 2, 2) True in Python 3?
PythonPython 3.xRangeIdentityPython InternalsPython Problem Overview
Why do ranges which are initialized with different values compare equal to one another in Python 3?
When I execute the following commands in my interpreter:
>>> r1 = range(0)
>>> r2 = range(2, 2, 2)
>>> r1 == r2
True
The result is True
. Why is this so? Why are two different range
objects with different parameter values treated as equal?
Python Solutions
Solution 1 - Python
range
objects are special:
The Python will compare range
objects as Sequences. What that essentially means is that the comparison doesn't evaluate how they represent a given sequence but rather what they represent.
The fact that the start
, stop
and step
parameters are completely different plays no difference here because they all represent an empty list when expanded:
For example, the first range
object:
list(range(0)) # []
and the second range
object:
list(range(2, 2, 2)) # []
Both represent an empty list and since two empty lists compare equal (True
) so will the range
objects that represent them.
As a result, you can have completely different looking range
objects; if they represent the same sequence they will compare equal:
range(1, 5, 100) == range(1, 30, 100)
Both represent a list with a single element [1]
so these two will also compare equal.
range
objects are really special:
No, Do note, though, that even though the comparison doesn't evaluate how they represent a sequence the result of comparing can be achieved using solely the values of start
, step
along with the len
of the range
objects; this has very interesting implications with the speed of comparisons:
r0 = range(1, 1000000)
r1 = range(1, 1000000)
l0 = list(r0)
l1 = list(r1)
Ranges compares super fast:
%timeit r0 == r1
The slowest run took 28.82 times longer than the fastest. This could mean that an intermediate result is being cached
10000000 loops, best of 3: 160 ns per loop
on the other hand, the lists..
%timeit l0 == l1
10 loops, best of 3: 27.8 ms per loop
Yeah..
As @SuperBiasedMan noted, this only applies to the range objects in Python 3. Python 2 range()
is a plain ol' function that returns a list while the 2.x
xrange
object doesn't have the comparing capabilies (and not only these..) that range
objects have in Python 3.
Look at @ajcr's answer for quotes directly from the source code on Python 3 range
objects. It's documented in there what the comparison between two different ranges actually entails: Simple quick operations. The range_equals
function is utilized in the range_richcompare
function for EQ
and NE
cases and assigned to the tp_richcompare
slot for PyRange_Type
types.
I believe the implementation of range_equals
is pretty readable (because it is nice as simple) to add here:
/* r0 and r1 are pointers to rangeobjects */
/* Check if pointers point to same object, example:
>>> r1 = r2 = range(0, 10)
>>> r1 == r2
obviously returns True. */
if (r0 == r1)
return 1;
/* Compare the length of the ranges, if they are equal
the checks continue. If they are not, False is returned. */
cmp_result = PyObject_RichCompareBool(r0->length, r1->length, Py_EQ);
/* Return False or error to the caller
>>> range(0, 10) == range(0, 10, 2)
fails here */
if (cmp_result != 1)
return cmp_result;
/* See if the range has a lenght (non-empty). If the length is 0
then due to to previous check, the length of the other range is
equal to 0. They are equal. */
cmp_result = PyObject_Not(r0->length);
/* Return True or error to the caller.
>>> range(0) == range(2, 2, 2) # True
(True) gets caught here. Lengths are both zero. */
if (cmp_result != 0)
return cmp_result;
/* Compare the start values for the ranges, if they don't match
then we're not dealing with equal ranges. */
cmp_result = PyObject_RichCompareBool(r0->start, r1->start, Py_EQ);
/* Return False or error to the caller.
lens are equal, this checks their starting values
>>> range(0, 10) == range(10, 20) # False
Lengths are equal and non-zero, steps don't match.*/
if (cmp_result != 1)
return cmp_result;
/* Check if the length is equal to 1.
If start is the same and length is 1, they represent the same sequence:
>>> range(0, 10, 10) == range(0, 20, 20) # True */
one = PyLong_FromLong(1);
if (!one)
return -1;
cmp_result = PyObject_RichCompareBool(r0->length, one, Py_EQ);
Py_DECREF(one);
/* Return True or error to the caller. */
if (cmp_result != 0)
return cmp_result;
/* Finally, just compare their steps */
return PyObject_RichCompareBool(r0->step, r1->step, Py_EQ);
I've also scattered some of my own comments here; look at @ajcr's answer for the Python equivalent.
Solution 2 - Python
Direct quote from the docs (emphasis mine): > Testing range objects for equality with == and != compares them as > sequences. That is, two range objects are considered equal if they > represent the same sequence of values. (Note that two range objects > that compare equal might have different start, stop and step > attributes, for example range(0) == range(2, 1, 3) or range(0, 3, 2) > == range(0, 4, 2).)
If you compare range
s with the "same" list, you'll get inequality, as stated in the docs as well:
> Objects of different types, except different numeric types, never > compare equal.
Example:
>>> type(range(1))
<class 'range'>
>>> type([0])
<class 'list'>
>>> [0] == range(1)
False
>>> [0] == list(range(1))
True
Note that this explicitly only applies to Python 3. In Python 2, where range
just returns a list, range(1) == [0]
evaluates as True
.
Solution 3 - Python
To add a few additional details to the excellent answers on this page, two range
objects r0
and r1
are compared roughly as follows:
if r0 is r1: # True if r0 and r1 are same object in memory
return True
if len(r0) != len(r1): # False if different number of elements in sequences
return False
if not len(r0): # True if r0 has no elements
return True
if r0.start != r1.start: # False if r0 and r1 have different start values
return False
if len(r0) == 1: # True if r0 has just one element
return True
return r0.step == r1.step # if we made it this far, compare step of r0 and r1
The length of a range
object is easily to calculate using the start
, stop
and step
parameters. In the case where start == stop
, for example, Python can immediately know that the length is 0. In non-trivial cases, Python can just do a simple arithmetic calculation using the start
, stop
and step
values.
So in the case of range(0) == range(2, 2, 2)
, Python does the following:
- sees that
range(0)
andrange(2, 2, 2)
are different objects in memory. - computes the length of both objects; both lengths are 0 (because
start == stop
in both objects) so another test is needed. - sees that
len(range(0))
is 0. This means thatlen(range(2, 2, 2))
is also 0 (the previous test for inequality failed) and so the comparison should returnTrue
.
Solution 4 - Python
res = range(0) == range(2, 2, 2)
Where:
range(0)
means the range from 0
to 0
- 0
steps (here step
equals to default value 1
), list without values.
range(2, 2, 2)
means the range from 2
to 2
with step equals to 2
, list without values.
So, these ranges are really equal
Solution 5 - Python
range(0)
returns range(0,0)
. You start from 0 up to 0 with step 1, which is undefined since the third argument can't be 0 [by default]. You can't reach 0 with 1. No counter's action in place, therefore 0.
range(2, 2, 2)
returns range(2, 2, 2)
. You start from 2 up to 2 but with step of 2. Which again, is basically 0 since you don't count up to anything.
range(0) == range(2,2,2)
True and exactly the same.