1

I am familiar with the difference between range() and xrange(). I noticed something weird with xrange():

>>> xrange(1,10,2)
xrange(1, 11, 2)

>>> xrange(1,10,4)
xrange(1, 13, 4)

Functionally, it is correct:

>>> for item in xrange(1,10,4):
...     print item
... 
1
5
9
>>>

However, as you can see, the stop value in the returned xrange object is the next higher value after the last legal value. Any reason why?

range() which now provides the same functionality in Python 3 as xrange in Python 2 behaves as expected:

>>> range(1,10,4)
range(1, 10, 4)
>>> range(1,10,2)
range(1, 10, 2)
>>> 
  • 2
    No, `xrange()` is not the same as `range()` in python 3. The latter is a new type. The end value is *never* included in either `range()` or `xrange()`. Because of your `step` value, neither `11` nor `10` are included in the range output anyway. – Martijn Pieters Mar 14 '13 at 12:50
  • @MartijnPieters I agree with your first half of the comment. Edited my question. –  Mar 14 '13 at 12:53
  • @MartijnPieters Now, regarding the stop value never included, yes I know that. Why is the xrange object returned with a stop value as the last legal value + step ? –  Mar 14 '13 at 12:54

3 Answers3

4

The stop value of a range or xrange is always exclusive.

Quote from the docs (Python 2):

If step is positive, the last element is the largest start + i * step less than stop; if step is negative, the last element is the smallest start + i * step greater than stop.

And for Python 3:

For a positive step, the contents of a range r are determined by the formula r[i] = start + step*i where i >= 0 and r[i] < stop.

For a negative step, the contents of the range are still determined by the formula r[i] = start + step*i, but the constraints are i >= 0 and r[i] > stop.


About the second part of your question regarding the repr() of the xrange:

xrange(1, 10, 4) and xrange(1, 13, 4) are identical and repr() for native python objects usually returns valid python code to recreate the object. This does not need to be the exactly same python code that initially created the object.

Community
  • 1
  • 1
ThiefMaster
  • 310,957
  • 84
  • 592
  • 636
  • 1
    Yes, I will accept your answer since the second half of the answer is what I was looking for. Thanks. –  Mar 14 '13 at 12:57
  • Sorry, I accepted @zodiac's answer since he even pointed me to the source code which gives me the exact answer I was looking for. –  Mar 14 '13 at 13:28
3

xrange(1, 10, 4) is equivalent to xrange(1, 13, 4). To use your example:

>>> for item in xrange(1,13,4):
...     print item
... 
1
5
9
>>> 

xrange in Python 2 canonicalizes the start, stop, step arguments. Internally, the xrange implementation stores the triple start, step and length (number of elements in the xrange object) instead of start, step and stop. Here is how xrange.__repr__() is implemented [1]:

rtn = PyString_FromFormat("xrange(%ld, %ld, %ld)",
                          r->start,
                          r->start + r->len * r->step,
                          r->step);

[1] https://github.com/replit/empythoned/blob/master/cpython/Objects/rangeobject.c

xuanji
  • 5,007
  • 2
  • 26
  • 35
  • Great. Thanks for sharing the link to the CPython source. –  Mar 14 '13 at 13:23
  • I am accepting your answer, since it is precisely what my question was. –  Mar 14 '13 at 13:27
  • This is the link to the official CPython 3.3 source: http://hg.python.org/cpython/file/e45db319e590/Objects/rangeobject.c#l808 –  Mar 14 '13 at 13:36
  • And the CPython 2.7 source: http://hg.python.org/cpython/file/4edde40afee6/Objects/rangeobject.c#l133 –  Mar 14 '13 at 13:37
2

Does it really matter?

The effect is the same. Neither 10 nor 11 is included in the output of xrange(), and xrange(1, 11, 2) is equivalent to xrange(1, 10, 2).

The Python 2 range type (the result of xrange()) stores the range length, not the end value, so to create the repr output it calculates that end value for you. And because you used a step value, the calculation shows the result of the formula start + length * step. For the implementation, the length is the more important value, the end value can safely be discarded and recalculated as needed.

So, when you create xrange(1, 10, 2), it calculates the range length and stores that instead of the end value:

if (step > 0 && lo < hi)
return 1UL + (hi - 1UL - lo) / step;
else if (step < 0 && lo > hi)
return 1UL + (lo - 1UL - hi) / (0UL - step);
else
return 0UL;

The Python 3 Range object stores the end value in addition to the length, so you can query the object for it and display it in the repr output.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • I am not sure why you ask "Does it really matter?". I Know the effect is the same. That's not what I asked. Anyway. –  Mar 14 '13 at 12:58