Почему Python допускает индексы срезов вне диапазона для последовательностей?

Поэтому я просто наткнулся на то, что мне кажется странной особенностью Python, и хотел кое-что прояснить.

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

p = [1,2,3]
p[3:] = [4] 
p = [1,2,3,4]

Я полагаю, что это просто добавление этого значения к концу, верно?
Почему я могу это сделать, однако?

p[20:22] = [5,6]
p = [1,2,3,4,5,6]

И тем более это:

p[20:100] = [7,8]
p = [1,2,3,4,5,6,7,8]

Это просто кажется неправильной логикой. Похоже, это должно выдать ошибку!

Любое объяснение?
Это просто странная вещь, которую делает Python?
Есть ли цель?
Или я думаю об этом не так?

2 ответа

Решение

Часть вопроса о показателях вне диапазона

Логика среза автоматически обрезает индексы по длине последовательности.

Разрешение индексов срезов расширяться за конечные точки было сделано для удобства. Было бы больно иметь диапазон проверки каждого выражения, а затем корректировать пределы вручную, поэтому Python сделает это за вас.

Рассмотрим случай использования отображения не более 50 первых символов текстового сообщения.

Самый простой способ (что сейчас делает Python):

preview = msg[:50]

Или трудный путь (проверяйте лимит самостоятельно):

n = len(msg)
preview = msg[:50] if n > 50 else msg

Вручную реализовать эту логику для настройки конечных точек было бы легко забыть, было бы легко ошибиться (обновление 50 в двух местах), было бы многословно и было бы медленно. Python перемещает эту логику во внутренности, где она лаконична, автоматическая, быстрая и правильная. Это одна из причин, по которой я люблю Python:-)

Часть вопроса относительно несоответствия длины назначений длине ввода

ФП также хотел знать обоснование для разрешения назначений, таких как p[20:100] = [7,8] где цель назначения имеет другую длину (80), чем длина замещающих данных (2).

Легче всего увидеть мотивацию по аналогии со строками. Рассматривать, "five little monkeys".replace("little", "humongous"), Обратите внимание, что у цели "маленький" есть только шесть букв, а у "огромных" - девять. Мы можем сделать то же самое со списками:

>>> s = list("five little monkeys")
>>> i = s.index('l')
>>> n = len('little')
>>> s[i : i+n ] = list("humongous")
>>> ''.join(s)
'five humongous monkeys'

Это все сводится к удобству.

До появления методов copy() и clear() это были популярные выражения:

s[:] = []           # clear a list
t = u[:]            # copy a list

Даже сейчас мы используем это для обновления списков при фильтрации:

s[:] = [x for x in s if not math.isnan(x)]   # filter-out NaN values

Надеюсь, что эти практические примеры дают хорошее представление о том, почему нарезка работает так, как она работает.

В документации есть ваш ответ:

s[i:j] ломтик s от i в j (примечание (4))

(4) ломтик s от i в j определяется как последовательность элементов с индексом k такой, что i <= k < j, Если i или же j больше, чем len(s) использовать len(s), Если i опущен или None использовать 0, Если j опущен или None использовать len(s), Если i Больше или равно j, ломтик пуст.

Документация IndexError подтверждает это поведение:

исключение IndexError

Возникает, когда нижний индекс последовательности находится вне диапазона. (Индексы слайса молча усекаются, чтобы попасть в допустимый диапазон; если индекс не является целым числом, TypeError Поднялся.)

По сути, такие вещи, как p[20:100] сокращается до p[len(p):len(p], p[len(p):len(p] является пустым срезом в конце списка, и присвоение ему списка изменит конец списка, чтобы он содержал указанный список. Таким образом, это работает как добавление / расширение исходного списка.

Это поведение аналогично тому, что происходит, когда вы назначаете список пустому фрагменту в любом месте исходного списка. Например:

In [1]: p = [1, 2, 3, 4]

In [2]: p[2:2] = [42, 42, 42]

In [3]: p
Out[3]: [1, 2, 42, 42, 42, 3, 4]
Другие вопросы по тегам