Чому діапазон (початок, кінець) не включає кінець?


305
>>> range(1,11)

дає тобі

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

Чому б не 1-11?

Вони просто вирішили зробити це так випадково або це має якесь значення, якого я не бачу?


11
читайте Dijkstra, ewd831
SilentGhost

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

32
Посилання на Dijkstra, ewd831: cs.utexas.edu/users/EWD/ewd08xx/EWD831.PDF
unutbu

35
@unutbu Ця стаття про Джикстра часто цитується в цій темі, але не надає тут нічого корисного, люди використовують її лише як звернення до влади. Єдиною відповідною псевдопричиною, яку він дає для питання ОП, є те, що він відчуває, що включення верхньої межі стає "неприродним" і "потворним" в конкретному випадку, коли послідовність у порожньому - це цілком суб'єктивна позиція і легко сперечається, тому це не приносить багато до столу. "Експеримент" з Месою не має великої цінності, не знаючи їх конкретних обмежень або методів оцінки.
sundar

6
@andreasdr Але навіть якщо косметичний аргумент справедливий, чи не підхід Python не вводить нову проблему читабельності? У загальній англійській мові термін "діапазон" означає, що щось варіюється від чогось до чогось - як інтервал. Цей len (список (діапазон (1,2))) повертає 1, а len (список (діапазон (2))) повертає 2 - це те, що ви дійсно повинні навчитися перетравлювати.
армін

Відповіді:


245

Тому що частіше називати той, 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).
moinudin

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

Хоча тут є кілька корисних алгоритмічних пояснень, я думаю, що це може допомогти додати кілька простих міркувань у реальному житті про те, чому це працює таким чином, що я вважаю корисним під час ознайомлення з темою для молодих новачків:

З чимось на зразок "діапазон (1,10)" плутанина може виникнути через думку, що пара параметрів являє собою "початок і кінець".

Це насправді початок і «зупинка».

Тепер, якби це було значення "кінець", так, так, ви можете очікувати, що це число буде включено як остаточний запис у послідовності. Але це не "кінець".

Інші помилково називають цей параметр "count", тому що якщо ви коли-небудь використовуватимете "range (n)", то він, звичайно, повторює "n" разів. Ця логіка руйнується, коли ви додаєте початковий параметр.

Тож ключовим моментом є запам’ятати його назву: « стоп ». Це означає, що це момент, коли досягнута, ітерація негайно припиниться. Не після цього моменту.

Отже, хоча "старт" справді являє собою перше значення, яке потрібно включити, після досягнення значення "стоп" воно "розбивається", а не продовжує обробляти "те", а також перед зупинкою.

Одна з аналогій, яку я використав, пояснюючи це дітям, - це те, що, за іронією долі, так краще вести себе, ніж діти! Вона не зупиняється після того, як вона повинна була - вона зупиняється негайно, не закінчуючи того, що робила. (Вони отримують це;))

Ще одна аналогія - коли ви керуєте автомобілем, ви не проходите знак зупинки / поступки / «поступайтеся дорогою» і в кінцевому підсумку сидите десь поруч або позаду свого автомобіля. Технічно ви досі не дійшли до цього, коли зупиняєтесь. Він не входить до «речей, які ти передав у дорогу».

Я сподіваюся, що щось із цього допоможе в поясненні Pythonitos / Pythonitas!


Це пояснення більш інтуїтивно зрозуміле. Спасибі
Фред

Дітям пояснення просто весело!
Антоні Хеткінс

1
Ви намагаєтеся нанести помаду на свиню. Розмежування "стоп" і "кінець" є абсурдним. Якщо я переходжу від 1 до 7, я не пройшов 7. Це просто недолік Python мати різні умови для позицій старту та зупинки. В інших мовах, включаючи людські, "від X до Y" означає "від X до 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
nobar

можливо, перелічений приклад слід прочитати, for index, item in enumerate(x)щоб уникнути плутанини
seans

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

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-стилю.

Також подобається асортимент Ruby:

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

Однак Рубі визнає, що багато разів вам потрібно буде включити термінальне значення і пропонує альтернативний синтаксис:

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

17
Гах! Я не використовую Ruby, але можу уявити, що 1..10проти 1...10читання коду важко розрізнити!
moinudin

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

11
Оператор діапазону Ruby абсолютно інтуїтивно зрозумілий. Чим довша форма отримує вам коротшу послідовність. кашель
Рассел Борогов

4
@Russell, можливо, 1 ............ 20 має дати той самий діапазон, що і 1..10. Тепер це було б синтаксичним цукром, на який варто перейти. ;)
кевпі

4
@Russell Додаткова крапка витісняє останній предмет із асортименту :)
Skilldrick

5

В основному в пітоні 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

за довжиною. Щоб отримати підрахунок, ви просто використовуєте ціле ділення.


Ці захисні сили неузгодженості Пітона є веселими. Якщо я хотів інтервал між двома числами, то чому я використовую віднімання, щоб отримати різницю замість інтервалу? Непослідовно використовувати різні умови індексації для початкової та кінцевої позицій. Навіщо вам писати "5:22", щоб отримати позиції 5 на 21?
bzip2

Це не Python, це досить поширене явище. На мові C, Java, Ruby, ви її називаєте
Арсеній

Я мав на увазі сказати, що для індексації це звичайно, а не те, що інші мови обов'язково мають такий самий точний вид об’єкта
Арсеній
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.