"Python usually just assigns. Some commands produce a shallow copy (e.g., slicing, sorted()
, dict .get()
, ...). A deep copy is produced by copy.deepcopy
, np.array
and some other commands."
How to improve that quote (of mine)? Mainly the second and third sentences require improving: how do you know (without always testing everything) which commands produce a shallow copy, which a deep copy, and which no copy (i.e., refer to the original memory area)?
The rest below is just an elaboration of the above.
Some more examples:
The dictionary methods .pop()
, and .popitem()
produce the original reference (no copy). list2 = list1
(assignment) produces the original; i.e., both variable names point to the same memory object.
An ideal answer (or linked page) might provide a longer list of principles (and maybe also a list of nontrivial examples).
.append()
: c.append(d)
adds to c
a new pointer memory area slot that points to where d now points. In that sense, c
becomes a shallow copy of the sequence of appended elements (see below) but any such new c[k]
is not a copy. When c[k]
exists, c[k]=d
has the same effect.
Similarly, new class instances reserve new pointer memory areas but they typically get assigned to something old.
Some examples (I was asked to illustrate "original"):
x = [1, 2, 3]
y = x #"original": x & y have exactly the same contents (they point to the same memory area)
y[0] = 9 #Now x == [9, 2, 3] == y
import copy
z = copy.copy(x) #shallow copy, but no difference to deep copy here. (Completely independent from x, y.)
z[2] = 999
print(z) #[9, 2, 999]
print(x) #[9, 2, 3]
class LTest():
def __init__(self,li):
self.list = li
li1 = [1, 2, 3]
lT = LTest(li1) #this is a new object, but lT.list just gets assigned to li1
#(i.e., they now point to the same memory area (pointer array))
li1[0] = 99
print(lT.list) #[99, 2, 3]
li1 = [4, 5, 6] #makes li1 point to this newly created pointer array;
# does not affect the contents of the old pointer array (still pointed to by lT.list)
print(lT.list) #[99, 2, 3]
a=[[0, 0], [1, 5], [2, -5]]
b = sorted(a,key=lambda a:a[1]) #shallow "sorted copy" of a
# b[0][1]=9 would change a but b[2]=[7,8] would not change a.
c = [] #we'll make c a shallow copy of b (hence "of a")
for j in range(len(b)):
c.append(b[j]) #a new pointer slot in the c memory area, but it points to where b[j] pointed
# b[0][1]=9 would change a & c but b[2]=[7,8] would not change either.