Python числа Последовательности

shoplist = ['яблоки', 'манго', 'морковь', 'бананы']
name = 'swaroop'

print('Элементы с 1 по -1:', shoplist[1:-1])

Почему получается Элементы с 1 по -1: ['манго', 'морковь']

А при print('Элемент 1 -', shoplist[1]) и print('Элемент -1 -', shoplist[-1]) Получается Элемент 1 - манго и Элемент -1 - бананы


Ответы (1 шт):

Автор решения: Stanislav Volodarskiy

Что происходит

Принято соглашение: при указании диапазона левый конец (начало) включается в диапазон, а правый конец (конец) исключается. Например:

$ python
Python 3.10.0 (default, Oct 16 2021, 12:17:56) [GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
@>>> a = list(range(10))
@>>> a
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Если попросить срез со второго по пятый элемент (индексация с нуля, значения совпадают с индексами), то получатся значения 2, 3, 4, но не 5:

@>>> a[2:5]
[2, 3, 4]
@>>> a[2]
2
@>>> a[5]
5

Если попросить срез a[1:-1], то получатся значения от единицы до восьми. Последнее значение по индексу 9 (он же индекс -1) не включено:

@>>> a[1:-1]
[1, 2, 3, 4, 5, 6, 7, 8]
@>>> a[1]
1
@>>> a[-1]
9

Почему

Это соглашение. Просто авторы (я подозреваю, один единственный автор) приняли решение: все диапазоны должны быть полуинтервалами. В коде a[i:j], в нотации a[i,j). Левый конец включается, правый исключается.

Этому соглашению следует язык и его библиотека: срезы, range и многое другое. Оно вам может нравиться или не нравиться, но так сделано.

Это удобно когда ...

  • ... надо узнать сколько элементов в срезе [i, j). Это просто j - i. Если бы правый конец был включён, прилось бы добавлять единицу.

  • ... надо работать с пустым срезом. a[i:i] - пустой срез. Если бы правый конец был включён, прилось бы писать a[i:i - 1], что немного странно.

  • ... надо разбить список на части. a[:i] – левая часть, a[i:] – правая часть. Они не пересекаются по i-тому элементу.

Это очень не удобно когда ...

  • ... надо перевернуть срез задом наперёд. Был срез a[i:j], он же перевёрнутый – a[j - 1:i - 1:-1]. Это тихий ужас, но такое требуется не часто. Кроме того можно написать так: a[i:j][::-1].

История

У полуинтервалов длинная история. В C массивы байт (и вообще массивы) представлены парой указателей: первый включён, второй исключён. Из него даже читать нельзя. В С++ такая же история с итераторами. Правый конец всегда исключён. Например, vector::end() ссылается на элемент за концом вектора: итератор есть, читать из него нельзя.

В Питон полуинтервалы, я думаю, перекочевали из C. Сам CPython написан на C, разработчикам удобно что оба языка одинаково работают с интервалами.

Так уж сложилось, что языки (FORTRAN, ALGOL, PASCAL) в которых интервалы включают правый конец, выбыли из гонки (но есть и исключения). А их конкурент с нулевой индексацией и полуинтервалами жив и породил кучу последователей (это я про C).

Вот в доказательство письмо Дейкстры, в котором он поясняет как хороши полуинтервалы и индексация с нуля: https://www.cs.utexas.edu/~EWD/transcriptions/EWD08xx/EWD831.html. Ему можно верить, он придумал половину того, что сейчас называется программированием.

→ Ссылка