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

В чем разница между мелким копированием, глубокой копией и обычной операцией назначения?

import copy

a = "deepak"
b = 1, 2, 3, 4
c = [1, 2, 3, 4]
d = {1: 10, 2: 20, 3: 30}

a1 = copy.copy(a)
b1 = copy.copy(b)
c1 = copy.copy(c)
d1 = copy.copy(d)


print("immutable - id(a)==id(a1)", id(a) == id(a1))
print("immutable - id(b)==id(b1)", id(b) == id(b1))
print("mutable - id(c)==id(c1)", id(c) == id(c1))
print("mutable - id(d)==id(d1)", id(d) == id(d1))

Я получаю следующие результаты:

immutable - id(a)==id(a1) True
immutable - id(b)==id(b1) True
mutable - id(c)==id(c1) False
mutable - id(d)==id(d1) False

Если я выполню глубокую копию:

a1 = copy.deepcopy(a)
b1 = copy.deepcopy(b)
c1 = copy.deepcopy(c)
d1 = copy.deepcopy(d)

результаты такие же:

immutable - id(a)==id(a1) True
immutable - id(b)==id(b1) True
mutable - id(c)==id(c1) False
mutable - id(d)==id(d1) False

Если я работаю над операциями назначения:

a1 = a
b1 = b
c1 = c
d1 = d

тогда результаты:

immutable - id(a)==id(a1) True
immutable - id(b)==id(b1) True
mutable - id(c)==id(c1) True
mutable - id(d)==id(d1) True

Может кто-нибудь объяснить, в чем именно разница между копиями? Это связано с изменчивым & неизменяемые объекты? Если да, не могли бы вы объяснить это мне?

4b9b3361

Ответ 1

Нормальные операции присваивания просто указывают новую переменную на существующий объект. docs объясняют разницу между мелкой и глубокой копиями:

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

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

  • Глубокая копия создает новый составной объект, а затем рекурсивно вставляет в него копии объектов, найденных в оригинал.

Вот небольшая демонстрация:

import copy

a = [1, 2, 3]
b = [4, 5, 6]
c = [a, b]

Использование обычных операций присваивания для копирования:

d = c

print id(c) == id(d)          # True - d is the same object as c
print id(c[0]) == id(d[0])    # True - d[0] is the same object as c[0]

Использование мелкой копии:

d = copy.copy(c)

print id(c) == id(d)          # False - d is now a new object
print id(c[0]) == id(d[0])    # True - d[0] is the same object as c[0]

Использование глубокой копии:

d = copy.deepcopy(c)

print id(c) == id(d)          # False - d is now a new object
print id(c[0]) == id(d[0])    # False - d[0] is now a new object

Ответ 2

Для неизменяемых объектов нет необходимости копировать, потому что данные никогда не изменятся, поэтому Python использует одни и те же данные; Иды всегда одни и те же. Для изменяемых объектов, поскольку они могут потенциально меняться, [мелкая] копия создает новый объект.

Глубокая копия связана с вложенными структурами. Если у вас есть список списков, то deepcopy copies также вложенные списки, поэтому это рекурсивная копия. Просто копируя, у вас есть новый внешний список, но внутренние списки - это ссылки.

Назначение не копируется. Он просто устанавливает ссылку на старые данные. Поэтому вам нужно скопировать, чтобы создать новый список с тем же содержимым.

Ответ 3

Для неизменяемых объектов создание копии не имеет особого смысла, поскольку они не собираются изменяться. Для изменяемых объектов assignment, copy и deepcopy ведет себя по- разному. Давайте поговорим о каждом из них с примерами.

Операция присваивания просто назначает ссылку источника на место назначения, например:

>>> i = [1,2,3]
>>> j=i
>>> hex(id(i)), hex(id(j))
>>> ('0x10296f908', '0x10296f908') #Both addresses are identical

Теперь i и j технически относится к одному и тому же списку. И i и j имеют один и тот же адрес памяти. Любое обновление одного из них будет отражено другим. например:

>>> i.append(4)
>>> j
>>> [1,2,3,4] #Destination is updated

>>> j.append(5)
>>> i
>>> [1,2,3,4,5] #Source is updated

С другой стороны, copy и deepcopy создают новую копию переменной. Поэтому теперь изменения в исходной переменной не будут отражены в переменной копирования и наоборот. Однако copy(shallow copy) не создает копию вложенных объектов, а просто копирует ссылку на вложенные объекты. Deepcopy рекурсивно копирует все вложенные объекты.

Некоторые примеры, демонстрирующие поведение copy и deepcopy copy:

Пример плоского списка с использованием copy :

>>> import copy
>>> i = [1,2,3]
>>> j = copy.copy(i)
>>> hex(id(i)), hex(id(j))
>>> ('0x102b9b7c8', '0x102971cc8') #Both addresses are different

>>> i.append(4)
>>> j
>>> [1,2,3] #Updation of original list didn't affected copied variable

Пример вложенного списка с использованием copy :

>>> import copy
>>> i = [1,2,3,[4,5]]
>>> j = copy.copy(i)

>>> hex(id(i)), hex(id(j))
>>> ('0x102b9b7c8', '0x102971cc8') #Both addresses are still different

>>> hex(id(i[3])), hex(id(j[3]))
>>> ('0x10296f908', '0x10296f908') #Nested lists have same address

>>> i[3].append(6)
>>> j
>>> [1,2,3,[4,5,6]] #Updation of original nested list updated the copy as well

Пример плоского списка с использованием deepcopy :

>>> import copy
>>> i = [1,2,3]
>>> j = copy.deepcopy(i)
>>> hex(id(i)), hex(id(j))
>>> ('0x102b9b7c8', '0x102971cc8') #Both addresses are different

>>> i.append(4)
>>> j
>>> [1,2,3] #Updation of original list didn't affected copied variable

Пример deepcopy списка с использованием deepcopy :

>>> import copy
>>> i = [1,2,3,[4,5]]
>>> j = copy.deepcopy(i)

>>> hex(id(i)), hex(id(j))
>>> ('0x102b9b7c8', '0x102971cc8') #Both addresses are still different

>>> hex(id(i[3])), hex(id(j[3]))
>>> ('0x10296f908', '0x102b9b7c8') #Nested lists have different addresses

>>> i[3].append(6)
>>> j
>>> [1,2,3,[4,5]] #Updation of original nested list didn't affected the copied variable    

Ответ 4

Давайте посмотрим в графическом примере, как выполняется следующий код:

import copy

class Foo(object):
    def __init__(self):
        pass


a = [Foo(), Foo()]
shallow = copy.copy(a)
deep = copy.deepcopy(a)

enter image description here

Ответ 5

a, b, c, d, a1, b1, c1 и d1 являются ссылками на объекты в памяти, которые однозначно идентифицируются по их идентификаторам.

Операция присваивания принимает ссылку на объект в памяти и присваивает эту ссылку новому имени. c=[1,2,3,4] - это назначение, которое создает новый объект списка, содержащий эти четыре целых числа, и назначает ссылку на этот объект на c. c1=c - это присваивание, которое принимает ту же ссылку на один и тот же объект и присваивает его c1. Поскольку список изменен, все, что происходит с этим списком, будет видно независимо от того, обращаетесь ли вы к нему через c или c1, потому что оба они ссылаются на один и тот же объект.

c1=copy.copy(c) - это "мелкая копия", которая создает новый список и присваивает ссылку на новый список c1. c по-прежнему указывает на исходный список. Итак, если вы измените список в c1, список, на который ссылается c, не изменится.

Концепция копирования не имеет отношения к неизменяемым объектам, таким как целые числа и строки. Поскольку вы не можете изменять эти объекты, никогда не нужно иметь две копии одного и того же значения в памяти в разных местах. Поэтому целые числа и строки и некоторые другие объекты, к которым не применяется концепция копирования, просто переназначаются. Вот почему ваши примеры с a и b приводят к идентичным идентификаторам.

c1=copy.deepcopy(c) является "глубокой копией", но в этом примере он работает так же, как и мелкая копия. Глубокие копии отличаются от мелких копий тем, что мелкие копии сделают новую копию самого объекта, но любые ссылки внутри этого объекта сами не будут скопированы. В вашем примере ваш список содержит только целые числа внутри него (которые неизменяемы), и, как обсуждалось ранее, нет необходимости копировать их. Таким образом, "глубокая" часть глубокой копии не применяется. Однако рассмотрим этот более сложный список:

e = [[1, 2],[4, 5, 6],[7, 8, 9]]

Это список, содержащий другие списки (вы также можете описать его как двумерный массив).

Если вы запустите "мелкую копию" на e, скопировав ее на e1, вы обнаружите, что идентификатор списка изменяется, но каждая копия списка содержит ссылки на те же три списка - списки с целыми числами внутри. Это означает, что если вы должны были сделать e[0].append(3), то e будет [[1, 2, 3],[4, 5, 6],[7, 8, 9]]. Но e1 также будет [[1, 2, 3],[4, 5, 6],[7, 8, 9]]. С другой стороны, если вы впоследствии сделали e.append([10, 11, 12]), e будет [[1, 2, 3],[4, 5, 6],[7, 8, 9],[10, 11, 12]]. Но e1 все равно будет [[1, 2, 3],[4, 5, 6],[7, 8, 9]]. Это потому, что внешние списки представляют собой отдельные объекты, изначально каждый из которых содержит три ссылки на три внутренних списка. Если вы изменяете внутренние списки, вы можете увидеть эти изменения независимо от того, просматриваете ли вы их через одну копию или другую. Но если вы измените один из внешних списков, как указано выше, то e содержит три ссылки на исходные три списка плюс еще одну ссылку на новый список. И e1 по-прежнему содержит только исходные три ссылки.

"Глубокая копия" не только дублирует внешний список, но также входит в списки и дублирует внутренние списки, так что два результирующих объекта не содержат каких-либо одних и тех же ссылок (в отношении изменяемых объектов обеспокоены). Если в внутренних списках есть дополнительные списки (или другие объекты, такие как словари) внутри них, они тоже будут дублироваться. Это "глубокая" часть "глубокой копии".

Ответ 6

В python, когда мы назначаем объекты, такие как list, tuples, dict и т.д. Другому объекту, обычно с знаком '=', python создает копии по ссылке. То есть, скажем, у нас есть список таких списков:

list1 = [ [ 'a' , 'b' , 'c' ] , [ 'd' , 'e' , 'f' ]  ]

и мы присваиваем этот список другому списку:

list2 = list1

то, если мы напечатаем list2 в терминале python, получим следующее:

list2 = [ [ 'a', 'b', 'c'] , [ 'd', 'e', ' f ']  ]

Оба списка1 и list2 указывают на то же место в памяти, любое изменение на какое-либо из них приведет к изменениям, видимым в обоих объектах, то есть оба объекта указывают на то же место в памяти. Если мы изменим list1 следующим образом:

list1[0][0] = 'x
list1.append( [ 'g'] )

то оба списка1 и list2 будут:

list1 = [ [ 'x', 'b', 'c'] , [ 'd', 'e', ' f '] , [ 'g'] ]
list2 = [ [ 'x', 'b', 'c'] , [ 'd', 'e', ' f '] , [ 'g ] ]

Теперь, приходя к Shallow copy, когда два объекта копируются по мелкой копии, дочерний объект обоих родительских объектов относится к одному и тому же месту памяти, но любые новые изменения в любом из скопированного объекта будут независимыми друг от друга. Давайте рассмотрим это с помощью небольшого примера. Предположим, что у нас есть этот небольшой фрагмент кода:

import copy

list1 = [ [ 'a', 'b', 'c'] , [ 'd', 'e', ' f ']  ]      # assigning a list
list2 = copy.copy(list1)       # shallow copy is done using copy function of copy module

list1.append ( [ 'g', 'h', 'i'] )   # appending another list to list1

print list1
list1 = [ [ 'a', 'b', 'c'] , [ 'd', 'e', ' f '] , [ 'g', 'h', 'i'] ]
list2 = [ [ 'a', 'b', 'c'] , [ 'd', 'e', ' f '] ]

note2, список2 остается незатронутым, но если мы вносим изменения в дочерние объекты, такие как:

list1[0][0] = 'x

то оба списка1 и list2 получат изменение:

list1 = [ [ 'x', 'b', 'c'] , [ 'd', 'e', ' f '] , [ 'g', 'h', 'i'] ] 
list2 = [ [ 'x', 'b', 'c'] , [ 'd', 'e', ' f '] ]

Теперь Deep Copy помогает создавать полностью изолированные объекты друг от друга. Если два объекта копируются через Deep Copy, оба родителя и его дочерний объект будут указывать на другое место в памяти. Пример:

import copy

list1 = [ [ 'a', 'b', 'c'] , [ 'd', 'e', ' f ']  ]         # assigning a list
list2 = deepcopy.copy(list1)       # deep copy is done using deepcopy function of copy module

list1.append ( [ 'g', 'h', 'i'] )   # appending another list to list1

print list1
list1 = [ [ 'a', 'b', 'c'] , [ 'd', 'e', ' f '] , [ 'g', 'h', 'i'] ]
list2 = [ [ 'a', 'b', 'c'] , [ 'd', 'e', ' f '] ]

note2, список2 остается незатронутым, но если мы вносим изменения в дочерние объекты, такие как:

list1[0][0] = 'x

тогда также list2 не будет затронут, поскольку все дочерние объекты и родительские объекты указывают на другую ячейку памяти:

list1 = [ [ 'x', 'b', 'c'] , [ 'd', 'e', ' f '] , [ 'g', 'h', 'i'] ] 
list2 = [ [ 'a', 'b', 'c'] , [ 'd', 'e', ' f  ' ] ]

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

Ответ 7

Ниже код демонстрирует разницу между присваиванием, мелкой копией с использованием метода копирования, мелкой копией с использованием (срез) [:] и глубинной копии. Ниже пример использует вложенные списки там, делая различия более очевидными.

from copy import deepcopy

########"List assignment (does not create a copy) ############
l1 = [1,2,3, [4,5,6], [7,8,9]]
l1_assigned = l1

print(l1)
print(l1_assigned)

print(id(l1), id(l1_assigned))
print(id(l1[3]), id(l1_assigned[3]))
print(id(l1[3][0]), id(l1_assigned[3][0]))

l1[3][0] = 100
l1.pop(4)
l1.remove(1)


print(l1)
print(l1_assigned)
print("###################################")

########"List copy using copy method (shallow copy)############

l2 = [1,2,3, [4,5,6], [7,8,9]]
l2_copy = l2.copy()

print(l2)
print(l2_copy)

print(id(l2), id(l2_copy))
print(id(l2[3]), id(l2_copy[3]))
print(id(l2[3][0]), id(l2_copy[3][0]))
l2[3][0] = 100
l2.pop(4)
l2.remove(1)


print(l2)
print(l2_copy)

print("###################################")

########"List copy using slice (shallow copy)############

l3 = [1,2,3, [4,5,6], [7,8,9]]
l3_slice = l3[:]

print(l3)
print(l3_slice)

print(id(l3), id(l3_slice))
print(id(l3[3]), id(l3_slice[3]))
print(id(l3[3][0]), id(l3_slice[3][0]))

l3[3][0] = 100
l3.pop(4)
l3.remove(1)


print(l3)
print(l3_slice)

print("###################################")

########"List copy using deepcopy ############

l4 = [1,2,3, [4,5,6], [7,8,9]]
l4_deep = deepcopy(l4)

print(l4)
print(l4_deep)

print(id(l4), id(l4_deep))
print(id(l4[3]), id(l4_deep[3]))
print(id(l4[3][0]), id(l4_deep[3][0]))

l4[3][0] = 100
l4.pop(4)
l4.remove(1)

print(l4)
print(l4_deep)
print("##########################")
print(l4[2], id(l4[2]))
print(l4_deep[3], id(l4_deep[3]))

print(l4[2][0], id(l4[2][0]))
print(l4_deep[3][0], id(l4_deep[3][0]))

Ответ 8

GIST, который нужно предпринять, заключается в следующем: Работа с неглубокими списками (без суб_листов, только отдельных элементов) с использованием "нормального назначения" повышает "побочный эффект" при создании мелкого списка, а затем вы создаете копию этого списка, используя "нормальное назначение",, Этот "побочный эффект" - это когда вы меняете любой элемент созданного списка копий, потому что он автоматически изменит те же элементы исходного списка. То есть, когда copy пригодится, так как она не будет изменять исходные элементы списка при смене элементов копии.

С другой стороны, у copy также есть "побочный эффект", когда у вас есть список, в котором есть списки (sub_lists), и deepcopy решает его. Например, если вы создаете большой список с вложенными списками в нем (sub_lists) и создаете копию этого большого списка (исходный список). "Боковой эффект" возникнет, когда вы измените суб_листы списка копий, которые автоматически изменят суб_листы большого списка. Иногда (в некоторых проектах) вы хотите сохранить большой список (ваш исходный список), так как он без изменений, и все, что вам нужно, это сделать копию своих элементов (sub_lists). Для этого ваше решение - использовать deepcopy который позаботится об этом "побочном эффекте" и сделает копию без изменения исходного содержимого.

Различное поведение операций copy и deep copy относится только к составным объектам (т.е. К объектам, которые содержат другие объекты, такие как списки).

Вот различия, проиллюстрированные в этом простом примере кода:

Первый

проверьте, как ведет себя copy (мелкая), создав исходный список и копию этого списка:

import copy
original_list = [1, 2, 3, 4, 5, ['a', 'b']]
copy_list = copy.copy(original_list)

Теперь позвольте запустить некоторые тесты print и посмотреть, как ведет себя исходный список по сравнению с его списком копий:

original_list и copy_list имеют разные адреса

print(hex(id(original_list)), hex(id(copy_list))) # 0x1fb3030 0x1fb3328

Элементы original_list и copy_list имеют одинаковые адреса

print(hex(id(original_list[1])), hex(id(copy_list[1]))) # 0x537ed440 0x537ed440

sub_elements из original_list и copy_list имеют одинаковые адреса

print(hex(id(original_list[5])), hex(id(copy_list[5]))) # 0x1faef08 0x1faef08

изменение элементов original_list НЕ меняет элементы списка копий

original_list.append(6)
print("original_list is:", original_list) # original_list is: [1, 2, 3, 4, 5, ['a', 'b'], 6]
print("copy_list is:", copy_list) # copy_list is: [1, 2, 3, 4, 5, ['a', 'b']]

изменение элементов copy_list не изменяет элементы original_list

copy_list.append(7)
print("original_list is:", original_list) # original_list is: [1, 2, 3, 4, 5, ['a', 'b'], 6]
print("copy_list is:", copy_list) # copy_list is: [1, 2, 3, 4, 5, ['a', 'b'], 7]

изменение атрибутов original_list sub_elements автоматически изменяет sub_elements copy_list

original_list[5].append('c')
print("original_list is:", original_list) # original_list is: [1, 2, 3, 4, 5, ['a', 'b', 'c'], 6]
print("copy_list is:", copy_list) # copy_list is: [1, 2, 3, 4, 5, ['a', 'b', 'c'], 7]

изменение подкаталогов copy_list автоматически изменяет атрибуты original_list sub_elements

copy_list[5].append('d')
print("original_list is:", original_list) # original_list is: [1, 2, 3, 4, 5, ['a', 'b', 'c', 'd'], 6]
print("copy_list is:", copy_list) # copy_list is: [1, 2, 3, 4, 5, ['a', 'b', 'c', 'd'], 7]

второй

пусть проверяет, как ведет себя deepcopy, делая то же самое, что мы делали с copy (создавая исходный список и копию этого списка):

import copy
original_list = [1, 2, 3, 4, 5, ['a', 'b']]
copy_list = copy.copy(original_list)

Теперь позвольте запустить некоторые тесты print и посмотреть, как ведет себя исходный список по сравнению с его списком копий:

import copy
original_list = [1, 2, 3, 4, 5, ['a', 'b']]
copy_list = copy.deepcopy(original_list)

original_list и copy_list имеют разные адреса

print(hex(id(original_list)), hex(id(copy_list))) # 0x1fb3030 0x1fb3328

Элементы original_list и copy_list имеют одинаковые адреса

print(hex(id(original_list[1])), hex(id(copy_list[1]))) # 0x537ed440 0x537ed440

sub_elements из original_list и copy_list имеют разные адреса

print(hex(id(original_list[5])), hex(id(copy_list[5]))) # 0x24eef08 0x24f3300

изменение элементов original_list НЕ меняет элементы списка копий

original_list.append(6)
print("original_list is:", original_list) # original_list is: [1, 2, 3, 4, 5, ['a', 'b'], 6]
print("copy_list is:", copy_list) # copy_list is: [1, 2, 3, 4, 5, ['a', 'b']]

изменение элементов copy_list не изменяет элементы original_list

copy_list.append(7)
print("original_list is:", original_list) # original_list is: [1, 2, 3, 4, 5, ['a', 'b'], 6]
print("copy_list is:", copy_list) # copy_list is: [1, 2, 3, 4, 5, ['a', 'b'], 7]

модификация original_list sub_elements НЕ изменяет sub_elements copy_list

original_list[5].append('c')
print("original_list is:", original_list) # original_list is: [1, 2, 3, 4, 5, ['a', 'b', 'c'], 6]
print("copy_list is:", copy_list) # copy_list is: [1, 2, 3, 4, 5, ['a', 'b'], 7]

Модификация copy_list sub_elements НЕ изменяет sub_elements оригинала_list

copy_list[5].append('d')
print("original_list is:", original_list) # original_list is: [1, 2, 3, 4, 5, ['a', 'b', 'c', 'd'], 6]
print("copy_list is:", copy_list) # copy_list is: [1, 2, 3, 4, 5, ['a', 'b', 'd'], 7]

Ответ 9

Не уверен, упоминалось ли это выше или нет, но очень важно понять, что .copy() создает ссылку на оригинальный объект. Если вы меняете скопированный объект - вы меняете исходный объект. .deepcopy() создает новый объект и выполняет реальное копирование исходного объекта в новый. Изменение нового глубоко скопированного объекта не влияет на исходный объект.

И да,.deepcopy() рекурсивно копирует исходный объект, тогда как .copy() создает ссылочный объект для данных первого уровня исходного объекта.

Таким образом, разница между копированием и ссылками между .copy() и .deepcopy() значительна.

Ответ 10

>>lst=[1,2,3,4,5]

>>a=lst

>>b=lst[:]

>>> b
[1, 2, 3, 4, 5]

>>> a
[1, 2, 3, 4, 5]

>>> lst is b
False

>>> lst is a
True

>>> id(lst)
46263192

>>> id(a)
46263192 ------>  See here id of a and id of lst is same so its called deep copy and even boolean answer is true

>>> id(b)
46263512 ------>  See here id of b and id of lst is not same so its called shallow copy and even boolean answer is false although output looks same.