Assignment statements in Python do not copy objects, they create bindings between a target and an object. For collections that are mutable or contain mutable items, a copy is sometimes needed so one can change one copy without changing the other.

Consider the following program fragment:

a=[1,'2',[3]]
b=a[:]
print [id(x) for x in a,b] #[140550956421992, 140550956422136]
print [id(x) for x in a] #[11108696, 140551018996080, 140550956422280]
print [id(x) for x in b] #[11108696, 140551018996080, 140550956422280]

We can see that a and b is not the same object but all the elements they have are the same. Because slice copy is a shadow copy which returns a new object but does not create new objects for its elements.

When we make changes to the list in b, the corresponding list in a also changes:

b[0]+=111
b[1]+='222'
b[2]+=[3,3,3]
print b #[112, '2222', [3, 3, 3, 3]]
print a #[1, '2', [3, 3, 3, 3]]
print [id(x) for x in a] #[11108696, 140551018996080, 140550956422280]
print [id(x) for x in b] #[11110016, 140550956333660, 140550956422280]

Integer and String are not mutable objects, when we use += operator to modify them, they just return a new object. So the address has changed.

But list is mutable, when appending a new item to it, changes are made internally which will not create a new object. So, when b changes, a changes in the meantime.

Sometimes we don't want to a changes also, so we should make a deep copy from a. In the module copy, it provides two factory method:copy and deepcopy. copy is a shadow copy version which is the same as slice copy in list. And deepcopy is a version which creates a new object when copy an mutable object.

import copy
a=[1,2,[3]]
b=copy.deepcopy(a)
print a #[1, 2, [3]]
print b #[1, 2, [3]]
print [id(x) for x in a,b] #[140550956422496, 140550956422136]
print [id(x) for x in a] #[11108696, 11108672, 140551018855528]
print [id(x) for x in b] #[11108696, 11108672, 140550956421344]

When we change the list in b, a does not change:

b[2]+=[22,22,22]
print b #[1, 2, [3, 22, 22, 22]]
print a #[1, 2, [3]]

Differences are only relative to mutable objects when coping elements from compound objects . When we exchange list with tuple, deepcopy makes the same result as copy

a=[1,(2,)]
b=copy.copy(a)
c=copy.deepcopy(a)
print [id(x) for x in a] #[11108696, 140551018808080]
print [id(x) for x in b] #[11108696, 140551018808080]
print [id(x) for x in c] #[11108696, 140551018808080]

When a is a tuple:

a=(1,[2])
b=a
c=tuple(a)
d=copy.copy(a)
e=copy.deepcopy(a)

#[140306117732688, 140306117732688,140306117732688, 140306117732688,140306117729816]
print [id(x) for x in a,b,c,d,e]
print [id(x) for x in a] #[33198424, 140306117938064]
print [id(x) for x in b] #[33198424, 140306117938064]
print [id(x) for x in c] #[33198424, 140306117938064]
print [id(x) for x in d] #[33198424, 140306117938064]
print [id(x) for x in e] #[33198424, 140306117938424]

The Python Doc says that:

Two problems often exist with deep copy operations that don’t exist with shallow copy operations:

  • Recursive objects (compound objects that, directly or indirectly, contain a reference to themselves) may cause a recursive loop.

  • Because deep copy copies everything it may copy too much, e.g., administrative data structures that should be shared even between copies.

Examples lacking....