Странное поведение 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
.