flowchart LR A["name: nova_inventory"] --> C["one list object"] B["name: sol_inventory"] --> C C --> D["string: lantern"] C --> E["string: rope"]
Sharing, Changing, and Copying Objects Overview
1. One update appears in two places
Two explorers appear to have separate inventory names:
There is only one list. Assignment did not repeat the list construction; it bound sol_inventory to the object already reached through nova_inventory. Appending through either name changes that shared list.
Now compare equal independent values:
The lists contain equal values, but they are different objects. A mutation of one does not change the other. Unit 6 gives you the language and evidence to tell these situations apart before a surprising edit reaches the wrong data.
2. Names point; objects carry identity and state
Use a small diagram instead of imagining variables as sealed boxes. Names are ways to reach objects. Containers also hold references to other objects.
Both inventory names point to one mutable list. The list contains references to two string objects.
Every object has a type, a value or observable state, and an identity. The identity answers “is this the very same object?” It is distinct from asking whether two objects currently compare equal.
When code runs, watch two kinds of change:
- rebinding changes which object a name reaches;
- mutation changes the state of an existing object.
Those events can produce similar printed output while giving later code very different behavior.
3. Ask four questions before editing data
Before modifying a collection or passing it to a helper, ask:
- Are the values equal? Use
==for ordinary value comparison. - Are they the same object? Use
isonly when identity is the question. - Can this object change in place? Lists, dictionaries, and sets commonly can; strings, numbers, and tuples cannot change their own stored contents.
- Who should observe future changes? This ownership decision determines whether sharing, mutation, or copying is appropriate.
The last question prevents automatic rules such as “never mutate” or “always deep copy.” Shared state can be intentional. A scoreboard may need one list that several views observe. A new player’s inventory may need independent nested state. Correctness depends on the contract.
4. Choose a sharing policy, not a reflex
Four broad choices will recur:
| Choice | What stays shared? | Suitable when |
|---|---|---|
| Keep the reference | the complete object graph | every user should observe the same updates |
| Shallow copy | nested objects, but not the outer container | only outer membership or fields will change |
| Selective copy | only fields named by the ownership design | known mutable descendants need independence |
| Deep copy | a recursively copied supported graph | the complete mutable graph must be independently editable |
The most expensive or deepest choice is not automatically the safest design. Copying can hide intended communication, duplicate large structures, and obscure who owns the authoritative state. First draw which objects may change; then separate only the boundaries that require independent ownership.
5. Your path through this unit
| Lesson | Capability you will build | Main lab artifact |
|---|---|---|
| 1 | When Names Share the Same Object — follow bindings and container references, distinguish equality from identity, and use the None sentinel correctly. |
Party-roster object graph |
| 2 | Changing Objects and Choosing Safe Keys — distinguish mutation from rebinding, read method return contracts, and choose hashable dictionary/set keys. | Expedition-state tracker |
| 3 | Copying Nested Data Without Surprises — diagnose repeated references and select shallow, selective, or deep copying from ownership needs. | Independent mission boards |
| 4 | Designing Functions That Control Mutation — make caller-visible effects, returned copies, defaults, snapshots, and retained state explicit. | Quest-state API |
The Unit Challenge asks you to break a Mirror Vault whose doppelgängers accidentally share inventories, journals, and visited-room state.
6. Unit 5 supplied the call boundaries
You already know how to design parameters, returned results, side effects, defaults, closures, and decorated calls. Unit 6 explains which objects cross those boundaries.
The parameter badges is a local name, but it reaches the list supplied by the caller. Mutating that object is visible outside the function. Rebinding the local parameter would be different. Lesson 4 develops both traces and turns them into clear API choices.
7. Unit 7 will turn requirements into algorithms
This unit begins with an already-understood task and object graph. It asks how state is shared and who should control changes. Unit 7 adds ambiguous requirements, decomposition, algorithm selection, correctness arguments, and practical efficiency.
You will discuss the cost of copying qualitatively here, because it affects the ownership decision. Formal complexity and evidence-driven algorithm comparison remain in the next unit.
8. Set up an object laboratory
Separate fixture construction, relationships, mutations, and evidence:
# 1. Construct source objects.
source = {"name": "Nova", "badges": ["starter"]}
# 2. Establish a relationship deliberately.
alias = source
# 3. Record the expected relationship before mutation.
assert alias is source
assert alias["badges"] is source["badges"]
# 4. Perform one mutation.
alias["badges"].append("pathfinder")
# 5. Check values and relationships.
assert source["badges"] == ["starter", "pathfinder"]Keep value assertions and identity assertions separate. == can show that data matches while missing whether a later edit will leak. is can show shared identity while saying nothing about whether the current values are correct.
Restart the notebook before interpreting identity-sensitive examples. Old mutations and reused names can make a correct diagram appear wrong.
9. Choose a realistic pace
Unit 6 is planned for approximately 15–23 hours, including orientation, examples, diagrams, modifications, checkpoints, four final labs, and the challenge. The range is guidance rather than a deadline.
A useful rhythm is:
- draw names and container paths before running;
- predict both value equality and object identity;
- perform one rebind, mutation, or copy;
- update the graph rather than drawing a disconnected new picture;
- answer a checkpoint from the graph;
- finish the lab before opening its hint or solution; and
- restart before checking repeated calls or old shared state.
10. Check your readiness
Predict every assertion before running:
first = ["map"]
alias = first
equal_copy = ["map"]
assert first == alias
assert first is alias
assert first == equal_copy
assert first is not equal_copy
alias.append("key")
assert first == ["map", "key"]
assert equal_copy == ["map"]
alias = ["compass"]
assert first == ["map", "key"]
assert alias == ["compass"]Explain two moments separately. append changed the list reached through both aliased names. The later assignment changed only the alias binding; it did not undo or replace the object still reached through first.
You are ready for Lesson 1 when you can draw those relationships and explain why equal lists need not be identical.
Key points
- Names and container entries provide paths to objects; assignment can create another path without copying the object.
- Equality asks whether values compare equal; identity asks whether two expressions reach the same object.
- Rebinding changes a name-to-object relationship; mutation changes an existing object’s state.
- Copy depth should follow an ownership contract, not an automatic preference for the deepest copy.
- Unit 5 supplied function boundaries; this unit explains what can remain shared across them.