Mutable Data Lab

Practice reasoning about time-varying state: mutation vs rebinding, closures with nonlocal state, aliasing, identity vs equality, and the mutable-default-argument trap.

0. Predict: The Mutable Default Trap

**Predict the result** before running.

This function has a mutable default argument:

```python
def append_bad(item, bucket=[]):
    bucket.append(item)
    return bucket

def demo():
    a = append_bad(1)
    b = append_bad(2)
    fresh = append_bad(3, [])
    return a, b, (a is b), fresh
```

1) What does `demo()` return?
2) Why is `(a is b)` either `True` or `False`?

After you write your prediction, run the code to check.

  

1. Trace: Where Does the Balance Live?

This closure uses `nonlocal` to update a private `balance` over time.

```python
def make_withdraw(balance):
    def withdraw(amount):
        nonlocal balance
        if amount <= balance:
            balance -= amount
            return balance
        return "Insufficient funds"
    return withdraw
```

**Trace** `run()` by writing (in comments) what `balance` is after each call, and what each call returns.

Focus questions:
- Which frame stores `balance`?
- Does a failed withdrawal change `balance`?

Then run to confirm.

  

2. Extend: Password-Protected Account (SICP Ex 3.3)

Modify message-passing bank accounts to require a password.

Implement `make_account(balance, password)` so that:
- `acc(correct_pw, "withdraw")` returns a **function** that withdraws.
- `acc(correct_pw, "deposit")` returns a **function** that deposits.
- Any request with a wrong password returns the string **"Incorrect password"** (not a function).

**Predict (before running)** what each line should evaluate to:

```python
acc = make_account(100, "secret")
acc("secret", "deposit")(50)      # ?
acc("secret", "withdraw")(30)    # ?
acc("wrong", "withdraw")         # ?
```

Then implement and run the tests.