The 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.
No, 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.
Answer from Dimitris Fasarakis Hilliard on Stack OverflowUnderstanding Range() function in python. [Level: Absolute Beginner]
Why is range(0) == range(2, 2, 2) True in Python 3? - Stack Overflow
python - Looping over range(0) - Stack Overflow
Why is range(5) giving me range(0,5) instead of 0 1 2 3 4
Because that's how it's represented. It doesn't actually store all those values, only start, stop, and step.
More on reddit.comVideos
I first thought that it just returns numbers and you can do a lot of stuff with those numbers but now I am seeing that it is being used to run a loop a specific number of times. It's just not making sense to me why this works, like I can't intuitively understand it.
Tho I have started using in my code, an explanation on why it was developed this way would help.
The 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.
No, 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.
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 ranges 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.
range(a, b, s) is [a, a+s, a+2*s..., x] where x < b.
So range(0) -> range(0,0,1) generates an empty list. This means the inside of the for loop is skipped which returns True.
for i in range(n) means i shall iterate over the list [0,1,2....,n-1]. In your case range(0)=[] (an empty list) because there be no integer between 0 and 0. That's why this block of code is not getting executed:
for i in range(len(L) - 1): #empty list
if L[i] > L[i + 1]: #this line is not getting executed
return False
and this line is getting executed and it's returning True.
return True #this one is getting executed
In Python 3.x ,
range(0,3) returns a class of immutable iterable objects that lets you iterate over them, it does not produce lists, and they do not store all the elements in the range in memory, instead they produce the elements on the fly (as you are iterating over them) , whereas list(range(0,3)) produces a list (by iterating over all the elements and appending to the list internally) .
Example -
>>> range(0,3)
range(0, 3)
>>> list(range(0,3))
[0, 1, 2]
Ideally, if you only want to iterate over that range of values , range(0,3) would be faster than (list(range(0,3)) because the latter has the overhead of producing a list before you start iterating over it.
In Python 2.x , range(0,3) produces an list, instead we also had an xrange() function that has similar behavior of range() function from Python 3.x (xrange was renamed to range in Python 3.x)
For Python 3.5, From the documentation -
Range objects implement the collections.abc.Sequence ABC, and provide features such as containment tests, element index lookup, slicing and support for negative indices
So you can do things like -
>>> range(0,10)[5]
5
>>> range(0,10)[3:7]
range(3, 7)
>>> 5 in range(6,10)
False
>>> 7 in range(1,8)
True
And all of these are constant time operations , as can be seen from this test -
In [11]: %timeit a = xrange(0,1000000)[1000]
1000000 loops, best of 3: 342 ns per loop
In [12]: %timeit a = xrange(0,1000000)[10000]
1000000 loops, best of 3: 342 ns per loop
In [13]: %timeit a = xrange(0,1000000)[100000]
1000000 loops, best of 3: 342 ns per loop
In [14]: %timeit a = xrange(0,1000000)[999999]
1000000 loops, best of 3: 342 ns per loop
In [15]: %timeit a = xrange(0,10000000)[9999999]
1000000 loops, best of 3: 339 ns per loop
In [16]: %timeit a = xrange(0,1000000000000)[9999999999]
1000000 loops, best of 3: 341 ns per loop
It depends on what version of Python you are using.
In Python 2.x, range() returns a list, so they are equivalent.
In Python 3.x, range() returns an immutable sequence type, you need list(range(0,2)) to get a list.
Because it's more common to call range(0, 10) which returns [0,1,2,3,4,5,6,7,8,9] which contains 10 elements which equals len(range(0, 10)). There's a tendency in programming to use 0-based indexing.
Also, consider the following common code snippet:
for i in range(len(li)):
pass
Could you see that if range() went up to exactly len(li) that this would be problematic? The programmer would need to explicitly subtract 1. This also follows the common trend of programmers preferring for(int i = 0; i < 10; i++) over for(int i = 0; i <= 9; i++).
If you are calling range with a start of 1 frequently, you might want to define your own function:
>>> def range1(start, end):
... return range(start, end+1)
...
>>> range1(1, 10)
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Although there are some useful algorithmic explanations here, I think it may help to add some simple 'real life' reasoning as to why it works this way, which I have found useful when introducing the subject to young newcomers:
With something like 'range(1,10)' confusion can arise from thinking that pair of parameters represents the "start and end".
It is actually start and "stop".
Now, if it were the "end" value then, yes, you might expect that number would be included as the final entry in the sequence. But it is not the "end".
Others mistakenly call that parameter "count" because if you only ever use 'range(n)' then it does, of course, iterate 'n' times. This logic breaks down when you add the start parameter.
So the key point is to remember its name: "stop". That means it is the point at which, when reached, iteration will stop immediately. Not after that point.
So, while "start" does indeed represent the first value to be included, on reaching the "stop" value it 'breaks' rather than continuing to process 'that one as well' before stopping.
One analogy that I have used in explaining this to kids is that, ironically, it is better behaved than kids! It doesn't stop after it supposed to - it stops immediately without finishing what it was doing. (They get this ;) )
Another analogy - when you drive a car you don't pass a stop/yield/'give way' sign and end up with it sitting somewhere next to, or behind, your car. Technically you still haven't reached it when you do stop. It is not included in the 'things you passed on your journey'.
I hope some of that helps in explaining to Pythonitos/Pythonitas!