Почему диапазон (начало, конец) не включает конец?


305
>>> range(1,11)

дает тебе

[1,2,3,4,5,6,7,8,9,10]

Почему не 1-11?

Они просто решили сделать это случайным образом или это имеет какое-то значение, которого я не вижу?


11
прочитайте Дейкстра, ewd831
SilentGhost

11
В основном вы выбираете один набор отдельных ошибок для другого. Один набор с большей вероятностью приведет к преждевременному завершению ваших циклов, а другой может вызвать исключение (или переполнение буфера в других языках). После того, как вы написали кучу кода, вы увидите, что выбор поведения range()имеет смысл гораздо чаще
Джон Ла Руи

32
Ссылка на Дейкстру, ewd831: cs.utexas.edu/users/EWD/ewd08xx/EWD831.PDF
unutbu

35
@unutbu Эта статья Djikstra часто цитируется в этой теме, но здесь нет ничего ценного, люди используют ее просто как призыв к авторитету. Единственная уместная псевдо-причина, которую он приводит для вопроса ОП, заключается в том, что он чувствует, что включение верхней границы становится «неестественным» и «уродливым» в конкретном случае, когда последовательность в пустом - это совершенно субъективная позиция, и ее легко оспаривать, так что это не приносит много на стол. «Эксперимент» с Mesa не имеет особой ценности либо без знания их конкретных ограничений или методов оценки.
sundar - Восстановить Монику

6
@andreasdr Но даже если косметический аргумент верен, разве подход Python не создает новую проблему читабельности? В английском языке общего пользования термин «диапазон» подразумевает, что что-то колеблется от чего-то до чего-то - например, интервал. Этот len (list (range (1,2))) возвращает 1, а len (list (range (2))) возвращает 2 - это то, что вы действительно должны научиться переваривать.
Армин

Ответы:


246

Потому что более распространенным является вызов с range(0, 10)возвратом, [0,1,2,3,4,5,6,7,8,9]который содержит 10 равных элементов len(range(0, 10)). Помните, что программисты предпочитают индексирование на основе 0.

Также рассмотрим следующий общий фрагмент кода:

for i in range(len(li)):
    pass

Можете ли вы увидеть, что если бы range()дошло до того, len(li)что это было бы проблематично? Программист должен был бы явно вычесть 1. Это также следует общей тенденции программистов, предпочитающих for(int i = 0; i < 10; i++)более for(int i = 0; i <= 9; i++).

Если вы часто вызываете диапазон с начала 1, вы можете определить свою собственную функцию:

>>> def range1(start, end):
...     return range(start, end+1)
...
>>> range1(1, 10)
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

48
Если бы это было обоснование, параметры не были бы range(start, count)?
Марк Рэнсом

3
@shogun Начальное значение по умолчанию равно 0, то range(10)есть эквивалентно range(0, 10).
Мойнудин

4
Вы range1не будете работать с диапазонами, размер шага которых отличается от размера 1.
dimo414

6
Вы объясняете, что диапазон (x) должен начинаться с 0, а x будет «длиной диапазона». ХОРОШО. Но вы не объяснили, почему диапазон (x, y) должен начинаться с x и заканчиваться y-1. Если программист хочет цикл for с i в диапазоне от 1 до 3, он должен явно добавить 1. Это действительно удобство?
Армин

7
for i in range(len(li)):скорее антипаттерн. Надо использовать enumerate.
Ганс

27

Хотя здесь есть несколько полезных алгоритмических объяснений, я думаю, что это может помочь добавить несколько простых «реальных» рассуждений о том, почему это работает таким образом, что я нашел полезным при представлении предмета молодым новичкам:

С чем-то вроде «range (1,10)» может возникнуть путаница, если подумать, что пара параметров представляет «начало и конец».

Это фактически начало и «остановка».

Теперь, если бы это было «конечное» значение, то да, вы могли бы ожидать, что это число будет включено в качестве последней записи в последовательности. Но это не «конец».

Другие ошибочно называют этот параметр «count», потому что если вы когда-либо используете «range (n)», то он, конечно, повторяет «n» раз. Эта логика ломается, когда вы добавляете параметр запуска.

Таким образом, ключевой момент заключается в том, чтобы запомнить его название: « стоп ». Это означает, что это точка, в которой при достижении итерация будет немедленно остановлена. Не после этого момента.

Таким образом, хотя «start» действительно представляет первое значение, которое должно быть включено, при достижении значения «stop» оно «ломается», а не продолжает обрабатывать «то же самое» перед остановкой.

Одна аналогия, которую я использовал, объясняя это детям, заключается в том, что, по иронии судьбы, он ведет себя лучше, чем дети! Он не останавливается после того, как должен был - он останавливается немедленно, не заканчивая то, что делал. (Они получают это;))

Другая аналогия - когда вы ведете машину, вы не проходите мимо знака «стоп / выход / уступить дорогу» и в конечном итоге сидите рядом с вашей машиной или позади нее. Технически вы все еще не достигли этого, когда остановитесь. Это не входит в «вещи, которые вы прошли в своем путешествии».

Я надеюсь, что это поможет объяснить Pythonitos / Pythonitas!


Это объяснение более интуитивно понятно. Спасибо
Фред

Детское объяснение просто смешно!
Энтони Хэтчкинс

1
Вы пытаетесь нанести помаду на свинью. Различие между «стопом» и «концом» абсурдно. Если я перейду с 1 на 7, я не сдал 7. Это просто недостаток Python - иметь разные соглашения для стартовой и конечной позиций. На других языках, включая человеческие, «от Х до Y» означает «от Х до Y». В Python «X: Y» означает «X: Y-1». Если у вас встреча с 9 до 11, вы говорите людям, что это с 9 до 12 или с 8 до 11?
bzip2

24

Эксклюзивные диапазоны имеют ряд преимуществ:

С одной стороны, каждый элемент range(0,n)является допустимым индексом для списков длины n.

Также range(0,n)имеет длину n, не n+1включающую диапазон.


18

Хорошо работает в сочетании с индексацией на основе нуля и len() . Например, если у вас есть 10 элементов в списке x, они нумеруются от 0 до 9. range(len(x))дает вам 0-9.

Конечно, люди скажут вам, что делать больше на Pythonic for item in x или for index, item in enumerate(x)скорее чем for i in range(len(x)).

Срез также работает таким же образом: foo[1:4]это пункты 1-3 из foo(имея в виду, что пункт 1 на самом деле является вторым элементом из-за индексации на основе нуля). Для согласованности они оба должны работать одинаково.

Я думаю об этом как: «первый номер, который вы хотите, а затем первый номер, который вы вы не хотите». Если вы хотите 1-10, первое число, которое вы не хотите, это 11, так что это range(1, 11).

Если в конкретном приложении это становится громоздким, достаточно легко написать небольшую вспомогательную функцию, которая добавляет 1 к конечному индексу и вызовам range().


1
Согласитесь на нарезку. w = 'abc'; w[:] == w[0:len(w)]; w[:-1] == w[0:len(w)-1];
kevpie

def full_range(start,stop): return range(start,stop+1) ## helper function
Нобар

может быть, пример перечисления следует прочитать, for index, item in enumerate(x)чтобы избежать путаницы
Seans

@seans Спасибо, исправлено.
любезно

12

Это также полезно для разделения диапазонов; range(a,b)можно разделить на range(a, x)и range(x, b), тогда как с включенным диапазоном вы бы написали либо x-1или x+1. Хотя вам редко требуется разделять диапазоны, вы, как правило, довольно часто разделяете списки, что является одной из причин, по которой разделение списка l[a:b]включает в себя a-й элемент, но не b-й. Тогда rangeналичие того же свойства делает его хорошо согласованным.


11

Длина диапазона - это верхнее значение минус нижнее значение.

Это очень похоже на что-то вроде:

for (var i = 1; i < 11; i++) {
    //i goes from 1 to 10 in here
}

на языке C-стиля.

Также как и ассортимент Руби:

1...11 #this is a range from 1 to 10

Тем не менее, Ruby признает, что много раз вы захотите включить значение терминала, и предлагает альтернативный синтаксис:

1..10 #this is also a range from 1 to 10

17
Г! Я не использую Ruby, но я могу себе представить , что 1..10против 1...10того трудно различить при чтении коды!
Мойнудин

6
@marcog - когда вы знаете, что две формы существуют, ваши глаза настраиваются на разницу :)
Skilldrick

11
Оператор диапазона Руби совершенно интуитивно понятен. Чем длиннее форма, тем короче последовательность. кашель
Рассел Борогове

4
@Russell, возможно 1 ............ 20 должен давать тот же диапазон, что и 1..10. Теперь это будет какой-то синтаксический сахар, на который стоит перейти. ;)
kevpie

4
@Russell Дополнительная точка сжимает последний элемент из диапазона :)
Skilldrick

5

В основном в python range(n)итерирует nвремя, которое имеет исключительную природу, поэтому оно не дает последнего значения при печати, мы можем создать функцию, которая дает инклюзивное значение, это означает, что она также будет печатать последнее значение, упомянутое в диапазоне.

def main():
    for i in inclusive_range(25):
        print(i, sep=" ")


def inclusive_range(*args):
    numargs = len(args)
    if numargs == 0:
        raise TypeError("you need to write at least a value")
    elif numargs == 1:
        stop = args[0]
        start = 0
        step = 1
    elif numargs == 2:
        (start, stop) = args
        step = 1
    elif numargs == 3:
        (start, stop, step) = args
    else:
        raise TypeError("Inclusive range was expected at most 3 arguments,got {}".format(numargs))
    i = start
    while i <= stop:
        yield i
        i += step


if __name__ == "__main__":
    main()

4

Рассмотрим код

for i in range(10):
    print "You'll see this 10 times", i

Идея в том, что вы получите список длины y-x , который вы можете (как вы видели выше) повторить.

Читайте в документации по Python для диапазона - они рассматривают цикл итерации в качестве основного варианта использования.


1

Это просто удобнее рассуждать во многих случаях.

По сути, мы можем думать о диапазоне как об интервале между startи end. Если start <= endдлина интервала между ними равна end - start. Если бы на lenсамом деле была определена длина, вы бы имели:

len(range(start, end)) == start - end

Однако мы подсчитываем целые числа, включенные в диапазон, а не измеряем длину интервала. Чтобы оставить указанное выше свойство истинным, мы должны включить одну из конечных точек и исключить другую.

Добавление stepпараметра похоже на введение единицы длины. В этом случае вы ожидаете

len(range(start, end, step)) == (start - end) / step

для длины. Чтобы получить количество, вы просто используете целочисленное деление.


Эти защиты непоследовательности Python веселые. Если мне нужен интервал между двумя числами, зачем мне использовать вычитание, чтобы получить разницу вместо интервала? Неправильно использовать разные соглашения об индексации для начальной и конечной позиций. Зачем вам нужно писать «5:22», чтобы получить позиции с 5 по 21?
bzip2

Это не Python, это довольно распространено по всем направлениям. В C, Java, Ruby вы называете это
Арсений

Я хотел сказать, что это обычное дело для индексации, а не то, что другие языки обязательно имеют точно такой же тип объекта
Арсений
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.