androidengineers.Book a session

Python 03 · Collections

Mutability, aliasing, and safe copying

articleSelf-paced

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.

Guided workshop: avoid changing the source record

Suppose a preview adds a tag to an imported document. The original record must remain unchanged until the user commits. Assignment creates another reference, while a shallow copy duplicates only the outer container.

# academy-check
from copy import deepcopy

source = {"id": "guide-1", "tags": ["help"]}
shallow = source.copy()
shallow["tags"].append("preview")
assert source["tags"] == ["help", "preview"]

source = {"id": "guide-1", "tags": ["help"]}
preview = deepcopy(source)
preview["tags"].append("preview")
assert source["tags"] == ["help"]
assert preview["tags"] == ["help", "preview"]

Trace the first case as two dictionary objects pointing to the same list. In the second case the nested list is copied too. Deep copying is useful for this small JSON-like structure; it is not a universal strategy for database connections, open files, or large datasets. Often a clearer design creates exactly the new fields it needs: {**source, "tags": [*source["tags"], "preview"]}.

Exercise: deduplicate records by id, preserving the first record encountered. Given IDs a, b, a, return records a, b in that order and report one duplicate. Do not mutate the input list.

Worked approach: maintain seen = set() and result = []. For each record, increment a duplicate counter if its ID is in seen; otherwise add the ID and append the record. Appending keeps references to the original records, so document that callers must not mutate them or copy records at the boundary when isolation is required.

Checkpoint: explain the distinction between deduplicating by ID and by the complete record. Two records with the same ID but different text indicate an update or conflict, not necessarily an accidental duplicate. Add a test with two equal IDs and unequal text and specify which version your policy retains. The policy should be explicit before you introduce persistent storage.

YOUR LEARNING JOURNEY

0 of 121 available lessons completed

Progress saved in this browser. No account needed.
Mutability, aliasing, and safe copying | Agentic AI | Android Engineers