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)
prin
Normal assignment operations will simply point the new variable towards the existing object. The docs explain the difference between shallow and deep copies:
The difference between shallow and deep copying is only relevant for compound objects (objects that contain other objects, like lists or class instances):
A shallow copy constructs a new compound object and then (to the extent possible) inserts references into it to the objects found in the original.
A deep copy constructs a new compound object and then, recursively, inserts copies into it of the objects found in the original.
Here's a little demonstration:
import copy
a = [1, 2, 3]
b = [4, 5, 6]
c = [a, b]
Using normal assignment operatings to copy:
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]
Using a shallow copy:
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]
Using a deep copy:
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
Let's see in a graphical example how the following code is executed:
import copy
class Foo(object):
def __init__(self):
pass
a = [Foo(), Foo()]
shallow = copy.copy(a)
deep = copy.deepcopy(a)
Below code demonstrates the difference between assignment, shallow copy using the copy method, shallow copy using the (slice) [:] and the deepcopy. Below example uses nested lists there by making the differences more evident.
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]))
a, b, c, d, a1, b1, c1 and d1 are references to objects in memory, which are uniquely identified by their ids.
An assignment operation takes a reference to the object in memory and assigns that reference to a new name. c=[1,2,3,4]
is an assignment that creates a new list object containing those four integers, and assigns the reference to that object to c
. c1=c
is an assignment that takes the same reference to the same object and assigns that to c1
. Since the list is mutable, anything that happens to that list will be visible regardless of whether you access it through c
or c1
, because they both reference the same object.
c1=copy.copy(c)
is a "shallow copy" that creates a new list and assigns the reference to the new list to c1
. c
still points to the original list. So, if you modify the list at c1
, the list that c
refers to will not change.
The concept of copying is irrelevant to immutable objects like integers and strings. Since you can't modify those objects, there is never a need to have two copies of the same value in memory at different locations. So integers and strings, and some other objects to which the concept of copying does not apply, are simply reassigned. This is why your examples with a
and b
result in identical ids.
c1=copy.deepcopy(c)
is a "deep copy", but it functions the same as a shallow copy in this example. Deep copies differ from shallow copies in that shallow copies will make a new copy of the object itself, but any references inside that object will not themselves be copied. In your example, your list has only integers inside it (which are immutable), and as previously discussed there is no need to copy those. So the "deep" part of the deep copy does not apply. However, consider this more complex list:
e = [[1, 2],[4, 5, 6],[7, 8, 9]]
This is a list that contains other lists (you could also describe it as a two-dimensional array).
If you run a "shallow copy" on e
, copying it to e1
, you will find that the id of the list changes, but each copy of the list contains references to the same three lists -- the lists with integers inside. That means that if you were to do e[0].append(3)
, then e
would be [[1, 2, 3],[4, 5, 6],[7, 8, 9]]
. But e1
would also be [[1, 2, 3],[4, 5, 6],[7, 8, 9]]
. On the other hand, if you subsequently did e.append([10, 11, 12])
, e
would be [[1, 2, 3],[4, 5, 6],[7, 8, 9],[10, 11, 12]]
. But e1
would still be [[1, 2, 3],[4, 5, 6],[7, 8, 9]]
. That's because the outer lists are separate objects that initially each contain three references to three inner lists. If you modify the inner lists, you can see those changes no matter if you are viewing them through one copy or the other. But if you modify one of the outer lists as above, then e
contains three references to the original three lists plus one more reference to a new list. And e1
still only contains the original three references.
A 'deep copy' would not only duplicate the outer list, but it would also go inside the lists and duplicate the inner lists, so that the two resulting objects do not contain any of the same references (as far as mutable objects are concerned). If the inner lists had further lists (or other objects such as dictionaries) inside of them, they too would be duplicated. That's the 'deep' part of the 'deep copy'.
The GIST to take is this:
Dealing with shallow lists (no sub_lists, just single elements) using "normal assignment" rises a "side effect" when you create a shallow list and then you create a copy of this list using "normal assignment". This "side effect" is when you change any element of the copy list created, because it will automatically change the same elements of the original list. That is when copy
comes in handy, as it won't change the original list elements when changing the copy elements.
On the other hand, copy
does have a "side effect" as well, when you have a list that has lists in it (sub_lists), and deepcopy
solves it. For instance if you create a big list that has nested lists in it (sub_lists), and you create a copy of this big list (the original list). The "side effect" would arise when you modify the sub_lists of the copy list which would automatically modify the sub_lists of the big list. Sometimes (in some projects) you want to keep the big list (your original list) as it is without modification, and all you want is to make a copy of its elements (sub_lists). For that, your solution is to use deepcopy
which will take care of this "side effect" and makes a copy without modifying the original content.
The different behaviors of copy
and deep copy
operations concerns only compound objects (ie: objects that contain other objects such as lists).
Here are the differences illustrated in this simple code example:
First
let's check how copy
(shallow) behaves, by creating an original list and a copy of this list:
import copy
original_list = [1, 2, 3, 4, 5, ['a', 'b']]
copy_list = copy.copy(original_list)
Now, let's run some print
tests and see how the original list behave compared to its copy list:
original_list and copy_list have different addresses
print(hex(id(original_list)), hex(id(copy_list))) # 0x1fb3030 0x1fb3328
elements of original_list and copy_list have the same addresses
print(hex(id(original_list[1])), hex(id(copy_list[1]))) # 0x537ed440 0x537ed440
sub_elements of original_list and copy_list have the same addresses
print(hex(id(original_list[5])), hex(id(copy_list[5]))) # 0x1faef08 0x1faef08
modifying original_list elements does NOT modify copy_list elements
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']]
modifying copy_list elements does NOT modify original_list elements
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]
modifying original_list sub_elements automatically modify copy_list sub_elements
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]
modifying copy_list sub_elements automatically modify 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]
Second
let's check how deepcopy
behaves, by doing the same thing as we did with copy
(creating an original list and a copy of this list):
import copy
original_list = [1, 2, 3, 4, 5, ['a', 'b']]
copy_list = copy.copy(original_list)
Now, let's run some print
tests and see how the original list behave compared to its copy list:
import copy
original_list = [1, 2, 3, 4, 5, ['a', 'b']]
copy_list = copy.deepcopy(original_list)
original_list and copy_list have different addresses
print(hex(id(original_list)), hex(id(copy_list))) # 0x1fb3030 0x1fb3328
elements of original_list and copy_list have the same addresses
print(hex(id(original_list[1])), hex(id(copy_list[1]))) # 0x537ed440 0x537ed440
sub_elements of original_list and copy_list have different addresses
print(hex(id(original_list[5])), hex(id(copy_list[5]))) # 0x24eef08 0x24f3300
modifying original_list elements does NOT modify copy_list elements
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']]
modifying copy_list elements does NOT modify original_list elements
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]
modifying original_list sub_elements does NOT modify copy_list sub_elements
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]
modifying copy_list sub_elements does NOT modify 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', 'd'], 7]