Когда срез выполняет поверхностную копию, а когда — глубокую копию в Python 3

bs = [1, 2, 3]
print(id(bs))
print(id(bs[:]))
xs = bs[:]
xs[1] = [9, 9, 9]
print(bs)
print(xs)
-------------
4452573000
4452573064
[1, 2, 3]
[1, [9, 9, 9], 3]

Кажется, что bs[:] делает глубокую копию в xs

bs = [1, 2, 3]
print(id(bs))
print(id(bs[:])) 
xs = bs[:] = [4, 5, 6]
print(id(xs))
print(bs)
print(xs)
----------
4518600520
4518600584
4518600584
[4, 5, 6]
[4, 5, 6]

Кажется, что bs[:] делает поверхностную копию xs

bs[:] = [4, 5, 6] изменит исходный список bs на [4, 5, 6]. Но если просто сделать xs = bs[:] и xs[1] = [9, 9, 9], это не повлияет на исходный список bs, который по-прежнему [1,2,3]


person Hades    schedule 28.02.2019    source источник
comment
Значения ID гарантированно различны только для объектов с перекрывающимся временем жизни. id почти бесполезен для новичков, изучающих Python, потому что для извлечения любого полезного смысла из результатов требуется обманчиво высокий уровень понимания.   -  person user2357112 supports Monica    schedule 28.02.2019
comment
Фрагменты списка всегда создают поверхностные копии. С чего вы взяли, что кажется, что bs[:] делают глубокую копию xs???   -  person juanpa.arrivillaga    schedule 28.02.2019


Ответы (2)


Для списков и для большинства типов последовательностей при извлечении среза создается неглубокая копия нарезанного раздела списка. В xs = bs[:] xs становится копией bs

Назначение среза, с другой стороны, не создает копию среза. В bs[:] = [4, 5, 6] ни одна часть bs не копируется. Содержимое [4, 5, 6] назначается непосредственно в bs. (Это содержимое является ссылками на объекты int, а ссылки — это то, что копируется — мы не изменяем никакие целые числа.)


В связанном присваивании xs = bs[:] = [4, 5, 6] значением, присвоенным xs, является список, созданный выражением [4, 5, 6] справа, а не часть bs. Задание выполняется как

temp = [4, 5, 6]
xs = temp
bs[:] = temp

не как

bs[:] = [4, 5, 6]
xs = bs[:]

Извлечение фрагмента не выполняется, и копии bs не создаются.


Я не знаю ни одного типа в ядре языка Python, стандартной библиотеке Python или какой-либо широко используемой сторонней библиотеке, в которой при нарезке выполняется глубокое копирование. Некоторые типы, такие как представления памяти и массивы NumPy, возвращают представление данных исходного объекта для извлечения среза, но это даже меньше копии, чем поверхностная копия.

person user2357112 supports Monica    schedule 28.02.2019

Назначение из среза списка всегда поверхностное копирование, т. е. копирование ссылок в исходном списке.

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

xs[1] = [9, 9, 9]

Это изменяет xs[1] на ссылку на ваш новый список [9, 9, 9]. Неважно, каков был источник содержимого xs.

Это может иметь значение, если у вас есть список изменяемых объектов, и если вы используете для них методы мутации, а не переназначение:

bs = [[1], [2], [3]]
xs = bs[:] # now xs is a shallow copy of bs - it contains references to the same objects
xs[1].append(4)

Здесь xs[1] является ссылкой на тот же список, что и bs[1], поэтому вызов мутации повлияет на него независимо от того, какую ссылку вы используете для его получения.

person Peter DeGlopper    schedule 28.02.2019