I am learning Python and wrote this help
Ask
I am learning Python and wrote this helper function to collect user tags:
```python
def add_tag(tag, tag_list=[]):
tag_list.append(tag)
return tag_list
print(add_tag("python")) # Outputs: ['python']
print(add_tag("coding")) # Outputs: ['python', 'coding'] -- Why isn't it just ['coding']?!
```
I expected `tag_list` to be created fresh as an empty list every time I call `add_tag()` without the second argument. Instead, it seems to remember all previous items from earlier calls!
Can you provide:
1. Tonight's checkable 10-minute micro-goal: Explain the first principles of why default arguments in Python are evaluated once at function definition time rather than at runtime call time.
2. A complete minimal code demonstration showing the idiom using `None` as a sentinel value (`tag_list=None`).
3. One quick try-it challenge for me to write and test myself right now to confirm I got it?
Answer
**Tonight's 10-minute goal:** explain in one sentence why `def f(x=[])` shares the same list across calls, then run the `None`-sentinel fix and watch a fresh list appear each time.
**First principles (the why):**
Python evaluates the *default expression* once — at the moment the `def` line runs, when the function object is created. The list literal `[]` is built then and parked on the function as `f.__defaults__`. Every later call that omits `x` reuses that *same* list object, so `.append()` mutates a list everyone is sharing. Default values are not "fresh defaults per call" — they're "defaults bound once at definition."
Quick proof so it's not hand-waving:
```python
def stash(item, bag=[]):
bag.append(item)
print("id:", id(bag), "contents:", bag)
stash("a") # id: 1401... ['a']
stash("b") # same id, ['a', 'b'] ← mutation visible next call
```
Same `id(...)` both times = literally the same list object.
**The fix — `None` as a sentinel, list created inside the body:**
```python
def stash(item, bag=None):
if bag is None:
bag = []
bag.append(item)
return bag
print(stash("a")) # ['a']
print(stash("b")) # ['b'] ← fresh list, no leakage
```
The signature holds an immutable placeholder; the mutable thing is born *inside* the function, so each call gets its own.
**Your 10-minute try-it:** write a `keep_score(name, scores=None)` that, called twice in a row without the second argument, prints `['alice']` then `['bob']` — *not* a growing list. Paste your 4 lines and the two `print()` outputs. If both calls show separate lists, you've got it. If the second shows `['alice', 'bob']`, the sentinel check is missing or you typed `= []` somewhere — re-check the signature line.