Почему 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]