Learn the concept
Names refer to objects. Assigning one list to another name creates an alias, not an independent copy. Mutating the object is visible through both names. This is a common source of bugs in shared configuration, request state, and cached records.
A shallow copy creates a new outer container while keeping references to nested objects. A deep copy recursively copies more of the object graph, but it is not always the right domain behavior. For example, you may want an immutable record or a deliberate reconstruction of selected fields instead.
Prefer returning new data when ownership is unclear. If a function mutates its argument, make that contract explicit. Watch for mutable default arguments in functions; their lifetime is longer than a single call. The functions module will show the safe default pattern.
Run and inspect
original = {"tags": ["python"]}
alias = original
alias["tags"].append("ai")
assert original["tags"] == ["python", "ai"]
copy = {"tags": list(original["tags"])}
copy["tags"].append("new")
assert "new" not in original["tags"]
Your exercise
Create a batch of records, copy one record, and update a nested tag list. Demonstrate both accidental sharing and an intentional independent copy.
Check your understanding
Your explanation identifies which object is shared at every step, rather than describing all assignment as copying.