Python Functions and Scope Tracing Practice
Python functions pass arguments by object reference (`or pass-by-assignment`). Tracing how mutable vs immutable arguments behave when modified inside a function, alongside default parameter evaluation timing (`evaluated exactly once when the function is defined`), is vital for interview mastery.
Core Conceptual Insights
Theoretical Blueprint:Passing a mutable list into a function is like sharing the key to a storage locker. If the function puts a new box inside the locker (`.append()`), you see the box when you check the locker later. But if the function throws away their key and grabs a key to a new locker (`lst = []`), your original locker remains untouched.
Common Developer Trap:The most notorious Python bug is `def append_to(val, lst=[])`. Because default arguments are evaluated ONCE at function definition time, every call that omits `lst` shares and mutates the exact same default list object across all invocations!
Structured Practice Breakdown
Mutable default argument trap
def add_item(val, cache=[]):
cache.append(val)
return cache
print(add_item(1), add_item(2))[1, 2] [1, 2]Execution Walkthrough
Call 1 `add_item(1)` appends `1` to the shared default list `[1]`. Call 2 `add_item(2)` appends `2` to that same list `[1, 2]`. Since `print` evaluates both calls before outputting, both returned pointers show `[1, 2] [1, 2]`.
Pass by object reference (`immutables vs mutables`)
def process(num, lst):
num += 100
lst.append(99)
x = 5
y = [1, 2]
process(x, y)
print(x, y)5 [1, 2, 99]Execution Walkthrough
`num += 100` rebinds local `num` to `105`, leaving global `x` untouched at `5`. `lst.append(99)` mutates the list shared by `y` in-place. Output: `5 [1, 2, 99]`.
Keyword-only argument syntax (`*`)
def connect(host, *, port=8080):
return f'{host}:{port}'
try:
print(connect('localhost', 9000))
except TypeError:
print('Must use port=9000')Must use port=9000Execution Walkthrough
Because `port` is keyword-only, calling `connect('localhost', 9000)` raises `TypeError: connect() takes 1 positional argument but 2 were given`. Caught and printed.
`LEGB` scope and `UnboundLocalError`
count = 10
def check():
try:
print(count)
count = 20
except UnboundLocalError:
print('Local variable referenced before assignment')
check()Local variable referenced before assignmentExecution Walkthrough
Because `count = 20` exists inside `check()`, `count` is local. When `print(count)` runs before assignment, `UnboundLocalError` is raised and caught.