A Python name is a binding, not a box containing a copy
Python assignment binds names to objects, so mutating a shared list is visible through every alias while rebinding one name changes only that binding.
Assignment connects a name to an object
It is tempting to picture a variable as a box that owns its value. A more useful model is that names are bindings to objects. Python represents data as objects, each with identity, type and value; lists are mutable objects whose contents can change. [1]
When b = a runs, Python does not copy the list. It makes b refer to the same list object as a. At this point, using either name to mutate that list affects the object both names reach. The binding is shared; the list is not duplicated.
Mutation follows the shared reference
In the example, a begins as a list and b is bound to that same object. The call to a.copy() creates a new outer list for c. When b.append(3) mutates the shared list, printing a produces [1, 2, 3], while printing c produces [1, 2]. The copy captured the original sequence of elements before the append. Lists are mutable, and a shallow copy creates a separate outer container. [1]
The word “shallow” matters if elements are themselves mutable. For example, copying a list of dictionaries creates a new list but retains references to the same dictionaries. Replacing an element in one outer list does not replace it in the other, but mutating a shared dictionary is visible through both. Copy depth describes which reference edges are duplicated, not whether every object in a nested structure is independent.
a = [1, 2]
b = a
c = a.copy()
b.append(3)
print(a) # [1, 2, 3]
print(c) # [1, 2]
b = [9]
print(a is b) # False
Rebinding changes one name’s target
Later, b = [9] binds b to a newly created list. It does not reach backward and alter the original list or change what a refers to. The identity test a is b therefore prints False: these names now refer to distinct list objects. Python defines is as an identity comparison, while value equality asks whether objects compare equal. [1]
That distinction is practical when debugging state. If two lists have equal contents, a == b can be true even when a is b is false. Conversely, aliases can satisfy both identity and equality before either list changes. Use equality to ask whether values match; use identity when the question is whether two references denote the very same object.
Read the output as a reference trace
The example’s output is exactly [1, 2, 3], then [1, 2], then False. The first line follows mutation through an alias; the second follows the independent outer list made earlier; the last follows rebinding to a new list. Thinking in terms of object identity makes all three results predictable without imagining assignment as an implicit copy.
This model also clarifies APIs that accept mutable arguments. A function receiving a list can mutate the object visible to its caller, even if the local parameter has another name. If the function instead assigns a new list to its local parameter, that rebinding alone does not change the caller’s binding. The key question is always whether an operation changes an object or redirects a name.