I believe the simplest way to get what's happening is to use a visual representation(the idea of this representation is not mine, although I love it).
First of all you have to understand that in python there are only references to objects.
The objects themselves live separately one from the other. For example the list [0, 1] is a list-object that contains references to the object 0 and the object 1.
A reference is some kind of link. This is different from variables in other languages, since variables are generally memory locations where you put things. In python a "variable", i.e. an identifier, is simply a "name"(=reference) for an object.
To understand this, let's picture the relationships between objects with a metaphor:
Let's say that objects are heavy rocks in the sea, that are linked together by ropes and hooks (¿).
On the surface of the sea live the identifiers that refers to the objects. The identifiers are buoys that prevent objects from sinking in the depths(where, they say, sea monsters(aka the Garbage Collector) would destroy them).
For example, we can represent this situation:
a = [0, 1]
With the following diagram:
___
( )
~~~~~~~~( a )~~~~~~~~
(___)
o ¿ o
| O
| o
|
|
+------+-------+
| [ ¿ , ¿ ] |
+----|-----|---+
| |
| |
o | |
O | |
| |
+-+-+ +-+-+
| 0 | | 1 |
+---+ +---+
o O o
)
( ) o
) )( ) ( (
( ( )( ( ( ) )
As you can see the identifier a refers, i.e. is linked with a rope, to the list object.
The list-object has two slots, each containing a link connected to the objects 0 and 1.
Now, if we did:
b = a
The identifier b would refer to the same object of a:
___ ___
( ) ( )
~~~~~~~~~~~( a )~~~~~~~~~~~~~~~( b )~~~~~~~~~~~~~~~~
(___) (___)
¿ ¿
\ /
o \ / o
o \ / o
-------+-------
O | [ ¿ , ¿ ] | O
----|-----|----
| |
+-+-+ +-+-+
o | 0 | | 1 |
+---+ +---+ o
O
o O
o
)
) ( ) (
( ( )( ( ( )
( ) ) ( ) ( ( ) ) ( )
When you, instead, do a shallow copy of a, via:
b = a[:]
A new list is created, and its elements are copies of the references to the objects referred to by a, i.e. you made copies of the ropes, but they point to the same elements:
___ ___
( ) ( )
~~~~~~~~~~~( a )~~~~~~~~~~~~~~~( b )~~~~~~~~~~~~~~~~
(___) (___)
O ¿ ¿ o
| |
o | |
| |
-------+------ ------+-------
| [ ¿ , ¿ ] | | [ ¿ , ¿ ] |
----|----|---- ----|----|----
| | | |
\ \ / /
\ \ / /
\ \ / / o
o \ \ / / o
\ \ / / o
o \ \ / /
\ \ / / o
O \ X /
\ / \ /
\/ \/
| |
| |
| |
+-+-+ +-+-+
| 0 | | 1 |
+---+ +---+
)
( ( ) (
)( ) ) ) ( ( ) ) )
( ) ( ) ( ( ( ( ) ) ( ) ( ( (
Since integers are immutable, there isn't any difference between using copies or the same identical objects, but when you replace the integers with lists, which are mutable, you end up modifying references to the same object, hence the behaviour you see.
Visually, the code:
a = [[0, 1], [0, 1]]
b = a[:]
Results in:
___ ___
( ) ( )
~~~~~~~~~~~( a )~~~~~~~~~~~~~~~( b )~~~~~~~~~~~~~~~~
(___) (___)
O ¿ ¿ o
| |
o | |
| |
-------+------ ------+-------
| [ ¿ , ¿ ] | | [ ¿ , ¿ ] |
----|----|---- ----|----|----
| \ / |
| \ / |
| \ / |
| \ / |
| \ / |
| \ / |
| \ / |
| X |
| / \ |
| / \ |
| / \ |
| / \ |
| / \ |
| / \ |
| | \ |
| | | |
+----+-----+----+ +-----+----+----+
| [ ¿ , ¿ ] | | [ ¿ , ¿ ] |
+----|-----|----+ +----|-----|----+
\ \ / /
\ \ / /
\ \ / /
\ \ / /
\ \ / /
\ | / /
| |/ /
| X /
| / | /
| / | /
\ / \ /
Y Y
| |
+-+-+ +-+-+
| 0 | | 1 |
+---+ +---+
)
( ( ) (
)( ) ) ) ( ( ) ) )
( ) ( ) ( ( ( ( ) ) ( ) ( ( (
Note how the list b refers to the same sublists of a.
(implementation detail: CPython's bytecode compiler will optimize literal expressions, so that the same 0 and 1 objects are used in both sublists. There is also some caching involved for small integers, but this is not important. In the general case the sublists do not have all elements in common).
A deep copy is a copy that avoids this sharing of identical objects.
For example, after executing:
import copy
a = [[0, 1], [0, 1]]
b = copy.deepcopy(a)
The situation is:
___ ___
( ) ( )
~~~~~~~~~~~( a )~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~( b )~~~~~~~~~~~~~~~~
(___) (___)
O ¿ ¿ o
| |
o | |
| |
-------+------ -------+------
| [ ¿ , ¿ ] | | [ ¿ , ¿ ] |
----|----|---- ----|----|----
| \ | \
| \ | \
| \ | \
| \ | \
| \ | \
| \ | \
| \ | \
| \ | \
| \ | \
| \ | \
| \ | \
| \ | \
+----+----------+ +--+------------+ +----+----------+ +--+------------+
| [ ¿ , ¿ ] | | [ ¿ , ¿ ] | | [ ¿ , ¿ ] | | [ ¿ , ¿ ] |
+----|-----|----+ +----|-----|----+ +----|-----|----+ +----|-----|----+
\ \ / / \ \ / /
\ \ / / \ \ / /
\ \ / / \ \ / /
\ \ / / \ \ / /
\ \ / / \ \ / /
\ | / / \ | / /
| |/ / | |/ /
| X / | X /
| / | / | / | /
| / | / | / | /
\ / \ / \ / \ /
Y Y Y Y
| | | |
+-+-+ +-+-+ +-+-+ +-+-+
| 0 | | 1 | | 0 | | 1 |
+---+ +---+ +---+ +---+
) )
( ( ) ( ( ( ) (
)( ) ) ) ( ( ) ) ) )( ) ) ) ( ( ) ) )
( ) ( ) ( ( ( ( ) ) ( ) ( ( ( ( ) ( ) ( ( ( ( ) ) ( ) ( ( (
(Actually, it seems like copy.deepcopy is smart enough to avoid copying built-in objects that are immutable, such as int, long, tuples of immutable objects etc.
so all sublists share the same 0 and 1 objects)
Note that these diagrams might also help you to understand how reference counting works.
Every rope is a reference, and until an object has a chain of references that goes up to a buoy(i.e. an identifier) it stays alive. When there are no more ropes to link an object to the surface's buoys, then the objects sink, and gets destroyed by the garbage collector.