Почему p [:] был разработан, чтобы работать по-разному в этих двух ситуациях? - программирование
Подтвердить что ты не робот

Почему p [:] был разработан, чтобы работать по-разному в этих двух ситуациях?

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

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

4b9b3361

Ответ 1

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

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

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

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

а также

del p[:]

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

q = p[:]

Ответ 2

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

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

Ответ 3

Как утверждали другие; 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]

На самом деле это также упоминается в списке документов и в методе list.clear:

список. копия()

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

список. Чисто()

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

Ответ 4

В основном, синтаксис слайса может использоваться в 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 должен быть KeyError.

Заметка

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

object.__setitem__(self, key, value)

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

object.__delitem__(self, key)

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

(Акцент мой)

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

Ответ 5

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

q=p[:]

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

q=p

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

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

del p[:]

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

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

Ответ 6

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

В ранних версиях 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, если он когда-либо случится, может последовать его примеру. Это, как вы заметили, намного больше согласуется с другим поведением.