简体   繁体   中英

strange xrange() behavior in Python 2

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)
>>> 

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.

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.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM