p = [1,2,3]
print(p) # [1, 2, 3]

q=p[:]  # supposed to do a shallow copy
q[0]=11
print(q) #[11, 2, 3] 
print(p) #[1, 2, 3] 
# above confirms that q is not p, and is a distinct copy 

del p[:] # why is this not creating a copy and deleting that copy ?
print(p) # [] 

Выше подтверждает, что p[:] не работает одинаково в этих двух ситуациях. Не так ли?

Учитывая, что в следующем коде я ожидаю работать непосредственно с p, а не с копией p,

p[0] = 111
p[1:3] = [222, 333]
print(p) # [111, 222, 333]

Я чувствую

del p[:] 

Согласуется с p[:], все они ссылаются на исходный список но

q=p[:] 

Сбивает с толку (для новичков, как я), поскольку p[:] в этом случае приводит к новому списку!

Мое ожидание новичка было бы таким

q=p[:]

Должен быть таким же, как

q=p

Почему создатели позволили этому особому поведению создать копию?

53
2020 27 Июн 2019 в 02:33

6 ответов

Лучший ответ

Del и назначения разработаны последовательно, они просто не разработаны так, как вы ожидали. del никогда не удаляет объекты, он удаляет имена / ссылки (удаление объектов только когда-либо происходит косвенно, это сборщик ссылок / мусора, который удаляет объекты); Точно так же оператор присваивания никогда не копирует объекты, он всегда создает / обновляет имена / ссылки.

Оператор del и assignment принимает эталонную спецификацию (аналогично концепции lvalue в C, хотя детали отличаются). Эта справочная спецификация является либо именем переменной (простой идентификатор), либо ключом __setitem__ (объект в квадратных скобках), либо именем __setattr__ (идентификатор после точки). Это lvalue не оценивается как выражение, так как это сделает невозможным присвоение или удаление чего-либо.

Рассмотрим симметрию между:

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

И

del p[:]

В обоих случаях p[:] работает одинаково, потому что они оба оцениваются как l-значение. С другой стороны, в следующем коде p[:] является выражением, которое полностью вычисляется в объект:

q = p[:]
58
Lie Ryan 27 Июн 2019 в 22:35

Исторические причины, в основном.

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

Поэтому для срезов имеет смысл возвращать список при использовании в правой части выражения. a[i:j:s] возвратил новый список, содержащий выбранные элементы из a. И поэтому a[:] в правой части присвоения вернет новый список, содержащий все элементы a, то есть мелкую копию: это было совершенно непротиворечиво в то время.

С другой стороны, скобки на стороне слева выражения всегда изменяли исходный список: это был прецедент, установленный a[i] = d, и за этим прецедентом следовал del a[i], и затем del a[i:j].

Прошло время, и копирование значений и создание новых списков повсюду считалось ненужным и дорогостоящим. В настоящее время range() возвращает генератор, который выдает каждое число только в том виде, в котором он запрашивается, и итерация по фрагменту потенциально может работать аналогичным образом, но идиома copy = original[:] слишком хорошо укоренилась в качестве исторического артефакта.

Кстати, в Numpy это не так: ref = original[:] создает ссылку, а не поверхностную копию, что согласуется с тем, как del и назначение массивов работают.

>>> a = np.array([1,2,3,4])
>>> b = a[:]
>>> a[1] = 7
>>> b
array([1, 7, 3, 4])

Python 4, если он когда-либо случится, может последовать его примеру. Это, как вы заметили, намного больше согласуется с другим поведением.

2
Draconis 27 Июн 2019 в 00:05

Как утверждали другие; p[:] удаляет все элементы в p; НО не повлияет на q. Более подробно список документации приведен ниже.

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

>>> squares = [1, 4, 9, 16, 25]
...
>>> squares[:]
[1, 4, 9, 16, 25]

Поэтому q=p[:] создает (мелкую) копию p в виде отдельного списка, но при дальнейшей проверке он указывает на совершенно отдельное место в памяти.

>>> p = [1,2,3]
>>> q=p[:]
>>> id(q)
139646232329032
>>> id(p)
139646232627080

Это объясняется лучше в copy:

Мелкая копия создает новый составной объект, а затем (насколько это возможно) вставляет в него ссылки на объекты, найденные в оригинале.

Хотя del оператор выполняется рекурсивно для списков / фрагментов :

Удаление списка целей рекурсивно удаляет каждую цель слева направо.

Поэтому, если мы используем del p[:], мы удаляем содержимое p путем итерации по каждому элементу, тогда как q не изменяется, как указано ранее, он ссылается на отдельный список, хотя и имеет те же элементы:

>>> del p[:]
>>> p
[]
>>> q
[1, 2, 3]

На самом деле это также упоминается в списке документов в { {X0}} метод:

список . < сильный > копия ( )

Вернуть мелкую копию списка. Эквивалент a[:].

список . < сильный > ясно ( )

Удалить все элементы из списка. Эквивалент del a[:].

7
Jab 27 Июн 2019 в 00:26

del в итераторе - это просто вызов __delitem__ с индексом в качестве аргумента. Как и в скобках, вызов [n] является вызовом метода __getitem__ в экземпляре итератора с индексом n.

Поэтому, когда вы вызываете p[:], вы создаете последовательность элементов, а когда вы вызываете del p[:], вы отображаете эту del / __ delitem__ на каждый элемент в этой последовательности.

23
ipaleka 26 Июн 2019 в 23:58

В основном, синтаксис слайса может использоваться в 3 различных контекстах:

  • Доступ, т.е. x = foo[:]
  • Настройка, т. Е. foo[:] = x
  • Удаление, т.е. del foo[:]

И в этом контексте значения, заключенные в квадратные скобки, просто выбирают элементы. Это сделано для того, чтобы «срез» использовался последовательно в каждом из следующих случаев:

  • Поэтому x = foo[:] получает все элементы в foo и назначает их x. Это в основном мелкая копия.

  • Но foo[:] = x заменит все элементы в foo элементами в x.

  • А при удалении del foo[:] будут удалены все элементы в foo.

Однако это поведение можно настроить, как описано в 3.3.7. Типы эмулируемых контейнеров:

object.__getitem__(self, key)

Вызывается для выполнения оценки self[key] . Для типов последовательности принятыми ключами должны быть целые числа и объекты среза. Обратите внимание, что специальная интерпретация отрицательных индексов (если класс желает эмулировать тип последовательности) зависит от метода __getitem__(). Если ключ имеет неподходящий тип, TypeError может быть поднят; если значение выходит за пределы набора индексов для последовательности (после любой специальной интерпретации отрицательных значений), IndexError следует повысить. Для типов отображения, если ключ отсутствует (не в контейнере), KeyError должен быть поднят.

Запись

Циклы for ожидают, что IndexError будет вызван для недопустимых индексов, чтобы обеспечить правильное определение конца последовательности.

object.__setitem__(self, key, value)

Вызывается для выполнения назначения self[key] . То же самое, что и для __getitem__(). Это должно быть реализовано только для отображений, если объекты поддерживают изменения значений для ключей, или если новые ключи могут быть добавлены, или для последовательностей, если элементы могут быть заменены. Для неправильных значений ключа должны возникать те же исключения, что и для метода __getitem__().

object.__delitem__(self, key)

Вызывается для удаления self[key] . То же самое, что и для __getitem__(). Это должно быть реализовано только для отображений, если объекты поддерживают удаление ключей, или для последовательностей, если элементы могут быть удалены из последовательности. Для неправильных значений ключа должны возникать те же исключения, что и для метода __getitem__().

(Акцент мой)

Таким образом, теоретически любой тип контейнера может реализовать это, как захочет. Однако многие типы контейнеров следуют за реализацией списка.

6
MSeifert 27 Июн 2019 в 14:52

Я не уверен, если вы хотите такой ответ. Словом, для p [:] это означает «перебирать все элементы p». Если вы используете его в

q=p[:]

Тогда это может быть прочитано как «перебрать все элементы p и установить его в q». С другой стороны, используя

q=p

Просто означает «назначить адрес p для q» или «сделать q указателем на p», что сбивает с толку, если вы пришли из других языков, которые обрабатывают указатели индивидуально.

Поэтому, используя его в Del, как

del p[:]

Просто означает «удалить все элементы р».

Надеюсь это поможет.

2
Seraph Wedd 26 Июн 2019 в 23:48