Странное поведение xrange() в Python 2

Я знаком с разницей между range() и xrange(). Я заметил что-то странное с xrange():

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

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

Функционально это правильно:

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

Однако, как вы можете видеть, значение стопа в возвращаемом объекте xrange является следующим более высоким значением после последнего юридического значения. Любая причина, по которой?

range(), который теперь предоставляет ту же функциональность в Python 3, что и xrange в Python 2 ведет себя как ожидалось:

>>> range(1,10,4)
range(1, 10, 4)
>>> range(1,10,2)
range(1, 10, 2)
>>> 

Ответы

Ответ 1

xrange(1, 10, 4) эквивалентен xrange(1, 13, 4). Чтобы использовать ваш пример:

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

xrange в Python 2 канонизирует аргументы start, stop, step. Внутри реализация xrange хранит тройной старт, шаг и длину (количество элементов в объекте xrange) вместо начала, шага и остановки. Вот как реализовано xrange.__repr__() [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

Ответ 2

Значение остановки range или xrange всегда исключение.

Цитата из docs (Python 2):

Если step положительно, последний элемент является наибольшим start + i * step меньше stop; если step отрицательный, последний элемент является наименьшим start + i * step больше stop.

И для Python 3:

Для положительного step содержимое диапазона r определяется формулой r[i] = start + step*i, где i >= 0 и r[i] < stop.

Для отрицательного step содержимое диапазона все еще определяется формулой r[i] = start + step*i, но ограничения i >= 0 и r[i] > stop.


О второй части вашего вопроса относительно repr() xrange:

xrange(1, 10, 4) и xrange(1, 13, 4) идентичны, а repr() для собственных объектов python обычно возвращает действительный код python для воссоздания объекта. Это не обязательно должен быть точно такой же код python, который первоначально создавал объект.

Ответ 3

Это действительно имеет значение?

Эффект тот же. На выходе xrange() не включается ни 10, ни 11, а xrange(1, 11, 2) эквивалентно xrange(1, 10, 2).

Тип диапазона Python 2 (результат xrange()) сохраняет длину диапазона, а не конечное значение, поэтому для создания вывода repr он вычисляет это конечное значение для вас. И поскольку вы использовали значение шага, вычисление показывает результат формулы start + length * step. Для реализации длина является более важным значением, значение end можно безопасно отбросить и пересчитать по мере необходимости.

Итак, когда вы создаете xrange(1, 10, 2), он вычисляет длину диапазона и сохраняет это вместо конечного значения:

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;

Объект Python 3 Range хранит конечное значение в дополнение к длине, поэтому вы можете запросить объект для него и отобразить его в выводе repr.