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 range() Function The range() function returns a sequence of numbers, starting from 0 by default, and increments by 1 (by default), and stops before a specified number.
The range() method does not include the end number because it generates numbers up to the end number but never includes the end number in its result. To include the 10 number in your output, you need to get a range between 1 to 11.
Introduction. The range() is an in-built function in Python. It returns a sequence of numbers starting from zero and increment by 1 by default and stops before the given number. Now that we know the definition of range, let's see the syntax: range(start, stop, step)
The range() is a built-in function that returns a range object that consists series of integer numbers, which we can iterate using a for loop. In Python, Using a for loop with range() , we can repeat an action a specific number of times.
range
objects are special: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: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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With