Variable assignment (for example, a = b) in Python does not copy the object itself but merely creates a new name (reference) to the existing object. Changes through one name will be visible through the other if it is a mutable object.
Shallow copy, for example, through copy.copy(obj) or slicing [:] for a list, creates a new top-level object, but the nested objects inside are copied by reference (i.e., both structures "point" to the same sub-containers). If you modify the nested objects, the changes will be visible through both objects.
Example:
import copy lst1 = [[1,2], [3,4]] lst2 = copy.copy(lst1) # or lst1[:] lst1[0][0] = 100 print(lst2) # [[100, 2], [3, 4]]
lst2 is a new list, but its first element is the same nested list.
Question: What is the difference between lst2 = lst1[:] and lst2 = copy.copy(lst1)?
Answer: In practice, for ordinary (one-level) lists, there is no difference — both methods create a shallow copy of the list. However, for custom container classes, there may be different behaviors (for example, if a custom __copy__ method is implemented). Similarly, for other types (dict, set, etc.), using the specialized copy module is safer.
import copy lst1 = [1, 2, 3] lst2 = lst1[:] lst3 = copy.copy(lst1) print(lst2 == lst3) # True
Story
In a project with configuration processing, a developer duplicated default parameters through assignment params = default_params and expected to change them "in isolation." Ultimately, any changes in any copy led to a cascade of changes throughout the application since they were actually working with the same object.
Story
An inexperienced programmer used a shallow copy of a list to store the game state (game_states = states[:]). With a nested structure (lists of pieces on the board), changes inside one state " leaked" into others, breaking the history of rollbacks and move repetitions.
Story
When trying to clone data in an OOP application, the choice was between slicing and copy.copy(). But the structure encountered its own class with its own copy method, which was only considered when using copy.copy(). Slicing ignored the logic of copying the object, leading to non-obvious bugs.