Assignment, Shallow Copy, Or Deep Copy? | by Weilin Li | Towards Data Science

from copy import copy, deepcopy
The goal of this article is to describe what will happen in memory when we
Assign a variable B = A ,
Shallow copy it C = copy(A) , or
Deep copy it D = deepcopy(A).
I first describe a bit about memory management and optimization in Python. After laying the ground, I explain the difference between assignment statement, shallow copy, and deep copy. I then summarize the difference in a table.
If you prefer watching a video to reading an article, you can find the complementary video here.
int, float, list, dict, class instances, ... they are all objects in Python. In CPython implementation, built-in function id() returns memory address of an object —
>>> L1 = [1, 2, 3]>>> id(L1)3061530120
If we create a new variable L2 that refers to an object with the same value as L1, L2 will have a new memory address —
>>> L2 = [1, 2, 3]>>> id(L2)3061527304
Every time a new object is created, it will have a new memory address. Except when it is —
a very short stringan integer within [-5, 256]an empty immutable containers (e.g. tuples)
a very short string
an integer within [-5, 256]
an empty immutable containers (e.g. tuples)
Let’s see an example with an integer object. Both x and y refer to the same value 10. While L1 and L2 in the previous example had two different memory addresses, x and y share the same memory address —
>>> x = 10>>> y = 10>>> id(x)2301840>>> id(y)2301840
That’s because, in those three exceptions, Python optimizes memory by having the second variable refer to the same object in memory, — some call it “shared object”.
Keep the concept of shared object in mind because we’ll need it later when we create a deepcopy of an object.
It says in the Python documentation that “Assignment statements in Python do not copy objects, they create bindings between a target and an object.” That means when we create a variable by assignment, the new variable refers to the same object as the original variable does —
>>> A = [1, 2, [10, 11], 3, [20, 21]]>>> B = A>>> id(A)3061527080>>> id(B)3061527080
Because the new variable B and the original variable A share the same object (i.e. the same list), they also contain same elements —
>>> id(A[2])3061527368>>> id(B[2])3061527368
As illustrated in the figure below, A and B share the same id, i.e., they refer to the same object in memory. And they contain the same elements as well.
When we create a variable by shallow copy, the new variable refers to a new object —
>>> A = [1, 2, [10, 11], 3, [20, 21]]>>> C = copy(A)>>> id(A)3062428488>>> id(C)3062428520
Although A and C refers to two different objects (i.e. two lists with different memory addresses), elements in the two lists refer to the same objects —
>>> id(A[0])2301696>>> id(C[0])2301696>>> id(A[2])3062464904>>> id(C[2])3062464904
The figure below illustrates how elements in A refer to same objects as elements in C.
Similar to shallow copy, when we create a variable by deep copy, the new variable refers to a new object —
>>> A = [1, 2, [10, 11], 3, [20, 21]]>>> D = deepcopy(A)>>> id(A)3062727496>>> id(D)3062428488
As described in Python documentation —
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.
So different from shallow copy, elements in the two lists now refer to different objects —
>>> id(A[0])2301696>>> id(D[0])2301696>>> id(A[2])3062464648>>> id(D[2])3062466376
But why do A[0] and D[0] share the same object (i.e. have the same memory address)? Because they both refer to integers, which is one of the three exceptions due to memory optimization we mentioned in the beginning.
The figure below shows A and D refer to two different lists in memory, and elements in A refer to different objects than elements in D except integer elements due to memory optimization.
If there is one take-away from this article, it must be the table below. Variable assignment doesn’t copy objects, so A and B have the same memory address and contain same elements. Shallow copy creates a new object for C, but elements in C still refer to the same objects as elements in A. Deep copy also creates a new object for D, and elements in D refer to different objects than those elements in A with three types of exceptions.
This article is inspired by...
Python documentation on copy
Understanding Python variables and Memory Management