I am 35, an operations analyst learning

2026-09-18 · Anonymous · Doramagic.ai

Ask

I am 35, an operations analyst learning Python for data automation.
Today I ran into a confusing bug:
I had a list of lists representing a grid, like matrix_a = [[1, 2], [3, 4]].
I wanted to create a backup before modifying it, so I did matrix_b = matrix_a.copy() (or list(matrix_a)).
Then I modified matrix_b[0][0] = 99. To my shock, matrix_a[0][0] was also changed to 99!
Can you teach me why this happened:
1. Tonight's checkable micro-goal (understanding shallow vs deep copy in 1 concrete move).
2. A full novice demo with a different toy example showing what shallow copy copied (the outer box vs inner references) and what copy.deepcopy() does.
3. One small check/try for me to run right now in my terminal to verify I've got it.
4. Next step if I run into custom objects or dictionaries.

Answer

**Tonight's checkable goal (≈10 min)**: un one short script, predict which of three "copies" gets poked when you change an inner cell, and explain in one sentence why.

**Full demo — different toy, same shape:**

```python
import copy

team_a = [["alice", "bob"], ["cara", "dan"], ["eve", "fred"]]

shallow = team_a.copy() # outer box only
sliced = team_a[:] # same as .copy()
deep = copy.deepcopy(team_a) # every level

shallow[0][0] = "ZOE" # poke one inner cell on the shallow copy

print("team_a[0][0] =", team_a[0][0]) # ZOE <- surprised!
print("sliced[0][0] =", sliced[0][0]) # ZOE
print("deep[0][0] =", deep[0][0]) # alice <- untouched
```

You should see: `ZOE`, `ZOE`, `alice`.

**Why it happened to you too:** think of `team_a` as a *folder*. The folder holds three index cards. Each card has an *arrow* pointing to a small notebook with two names. `.copy()`, `list(...)`, and `[:]` are the same operation: they hand you a *new folder* with three *new index cards* — but every card still points to the **same three notebooks**. When you write "ZOE" into notebook #1 through `shallow`, you wrote into the notebook both folders' first card points to. `copy.deepcopy()` makes new folders, new cards, *and* new notebooks — three independent copies all the way down.

The two new ideas to lock in:
- `.copy()`, `list(x)`, `x[:]` → **one level deep**.
- `[row[:] for row in team_a]` → still one level on the outer list, but each inner list is duplicated. So poking an inner cell here is safe; appending a row to the outer list is also safe.
- `copy.deepcopy(x)` → walks every level reachable from `x` and clones it.

**Your turn — paste back the output and your one-sentence answer:**

Add **one line** to the script:
```python
shallow.append(["xena", "yuki"])
```
Before running, predict: will `team_a` now have 4 rows or still 3? Write the guess, run it, then tell me what you saw and why.

Why this is the right check: appending to the *outer* list of `shallow` does **not** touch `team_a`'s outer list — but earlier, modifying `shallow[0][0]` *did* touch `team_a[0][0]`. Same `.copy()`, two opposite outcomes — that's the whole "outer box vs inner references" idea in one try.

**Next step when you move on (dicts and your own objects):**
- **Dicts**: same rule. `dict_b = dict_a.copy()` copies the outer dict; nested dicts/lists still share. each for `copy.deepcopy(dict_a)` whenever nesting is in play.
- **Custom classes**: also the same rule, *unless* the class defines `__copy__` / `__deepcopy__`. A `eport` with `self.data = [[...]]` will leak through `.copy()` unless you deepcopy, override `__deepcopy__`, or store the inner data as something immutable (tuple, `frozenset`).
- A two-line check that works for any object: `obj_b is obj_a` (same outer box?) and `obj_b.inner is obj_a.inner` (same inner?). If both True, it's shallow at best.

One honest limit: deepcopy pays a real cost on huge graphs (it walks and clones every reachable node). For your ops work, that's almost always fine — but if you ever copy a 10M-row frame per call, switch to `pandas.DataFrame.copy(deep=True)` (which has the same trap: set `deep=False` and you'll get bitten by the same bug).