Подтвердить что ты не робот

Почему присвоение конца списка списка через срез не вызывает индексацию?

Я работаю над разреженной реализацией списка и недавно реализованным назначением через срез. Это заставило меня обнаружить какое-то поведение в реализации Python list, которое Я нахожу удивительный.

Учитывая пустой list и назначение через срез:

>>> l = []
>>> l[100:] = ['foo']

Я бы ожидал IndexError от list здесь, потому что способ, которым это реализовано, означает, что элемент не может быть извлечен из указанного индекса::

>>> l[100]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: list index out of range

'foo' не может быть даже извлечен из указанного фрагмента:

>>> l = []
>>> l[100:] = ['foo']
>>> l[100:]
[]

l[100:] = ['foo'] присоединяется к list (т.е. l == ['foo'] после этого назначения) и, похоже, ведет себя таким образом, поскольку исходный BDFL версия. Я не могу найти эту функциональность в любом месте (*), но и CPython и PyPy ведут себя таким образом.

Присвоение по индексу вызывает ошибку:

>>> l[100] = 'bar'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: list assignment index out of range

Итак, почему назначение после конца list через срез не поднимает IndexError (или какая-то другая ошибка, я думаю)?


Чтобы прояснить следующие первые два комментария, этот вопрос относится именно к присваиванию, а не к поиску (cf. Почему индекс подрезки за пределами диапазона работает в Python?).

Вдаваясь в соблазн угадать и присваивать 'foo' до l при индексе 0, когда я явно указал индекс 100, не следует обычным Zen Python.

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

Назначение среза до конца list ведет себя несколько иначе, чем в примере выше:

>>> l = [None, None, None, None]
>>> l[3:] = ['bar']
>>> l[3:]
['bar']

(*) Это поведение определено в Примечание 4 5.6. Типы последовательности в официальной документации (спасибо elethan), но это не объясняет, почему это было бы желательно при назначении.


Примечание.. Я понимаю, как работает поиск, и вы можете видеть, как желательно быть совместимым с этим при назначении, но я искал процитированную причину того, почему приписывание срезу будет вести себя в этом путь. l[100:] возвращает [] сразу после l[100:] = ['foo'], но l[3:] возвращает ['bar'] после l[3:] = ['bar'] поражает, если вы не знаете len(l), особенно если вы следуете за Python EAFP idiom.

4b9b3361

Ответ 1

Посмотрим, что на самом деле происходит:

>>> l = []
>>> l[100:] = ['foo']
>>> l[100:]
[]
>>> l
['foo']

Таким образом, назначение было действительно успешным, и элемент был помещен в список в качестве первого элемента.

Почему это происходит, потому что 100: в позиции индексации преобразуется в объект slice: slice(100, None, None):

>>> class Foo:
...     def __getitem__(self, i):
...         return i
... 
>>> Foo()[100:]
slice(100, None, None)

Теперь класс slice имеет метод indices (я не могу найти его документацию на Python в Интернете, хотя), который при заданной длине последовательности даст (start, stop, stride), который будет скорректирован для длина этой последовательности.

>>> slice(100, None, None).indices(0)
(0, 0, 1)

Таким образом, когда этот срез применяется к последовательности длины 0, он ведет себя точно так же, как срез slice(0, 0, 1) для фрагментов фрагментов, например. вместо foo[100:], вызывая ошибку, когда foo является пустой последовательностью, она ведет себя так, как если бы была запрошена foo[0:0:1] - это приведет к появлению пустого фрагмента при поиске.

Теперь код установщика должен работать правильно, когда l[100:] использовался, когда l - последовательность, содержащая более 100 элементов. Чтобы заставить его работать, проще всего не изобретать колесо и просто использовать механизм indices выше. Как недостаток, теперь он будет выглядеть немного странно в случаях кросс, но назначения срезов на срезы, которые являются "вне границ", будут помещены в конце текущей последовательности. (Однако выясняется, что в коде CPython мало повторного использования кода; list_ass_slice по существу дублирует всю эту обработку индекса, хотя он также будет доступен через объект C-объекта slice).

Таким образом: , если начальный индекс среза больше или равен длине последовательности, результирующий срез ведет себя так, как если бы он был срезом нулевой ширины, начиная с конца последовательности. I.e.: if a >= len(l), l[a:] ведет себя как l[len(l):len(l)] для встроенных типов. Это верно для каждого из присваивания, поиска и удаления.

Желательность этого заключается в том, что он не нуждается в каких-либо исключениях. В методе slice.indices не нужно обрабатывать какие-либо исключения - для последовательности длины l, slice.indices(l) всегда будет отображаться (start, end, stride) индексов, которые могут использоваться для любого из присваивания, поиска и удаления, и это что обе start и end равны 0 <= v <= len(l).

Ответ 2

Для индексирования возникает ошибка должна, если данный индекс является вне границ, потому что нет допустимого значения по умолчанию, которое может быть возвращено. (Нельзя возвращать None, потому что None может быть допустимым элементом последовательности).

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

Как указано в "Примечания к последовательности последовательностей" , если начальное или конечное значение среза больше, чем len(seq), тогда len(seq) используется вместо.

Итак, для a = [4, 5, 6] выражения a[3:] и a[100:] указывают на пустую подпоследовательность, следующую за последним элементом в списке. Однако после присвоения среза с использованием этих выражений они могут больше не ссылаться на одно и то же, поскольку длина списка может быть изменена.

Таким образом, после присваивания a[3:] = [7] срез a[3:] вернет [7]. Но после присвоения a[100:] = [8] срез a[100:] все равно вернет [], потому что len(a) все еще меньше 100. И учитывая все сказанное выше, это именно то, что следует ожидать, если поддерживать согласованность между назначением среза и извлечением фрагментов.