Python Scope
Where names live, how mutable arguments behave, and how closures capture variables.
Why it matters: Shared lists and late-bound closures cause bugs that only show up on the second call.
Basics
LEGB Rule
Python looks up names in this order:
- Local
- Enclosing (outer function)
- Global (module)
- Built-in (
len,print, ...)
Reading a global is fine. Assigning creates a new local unless you use global.
Common mistake: naming a variable list or sum and shadowing the built-in.
Tiny example
Read a global name from inside a function.
model = "mini"
def show():
print(model)
show()Basics
Mutable Arguments
Python passes object references.
Immutable values (int, str, tuple) cannot be changed by the callee.
Mutable values (list, dict, set) can. items.append(x) changes the caller's list. items = items + [x] does not.
Copy first with list(items) when you do not want shared changes.
Tiny example
Appending inside a function changes the caller's list.
batch = ["a"]
def add(items):
items.append("b")
add(batch)
print(batch)Basics
Closures
A closure is a nested function that keeps variables from the outer scope.
Closures capture the variable, not a snapshot of its value.
In loops, bind the value with a default: lambda s, t=t: s >= t.
Tiny example
A factory returns a filter closed over a threshold.
def make_keep(threshold):
def keep(score):
return score >= threshold
return keep
print(make_keep(0.7)(0.8))Then AI / ML
AI / ML examples
Same idea, used in real AI work. Press Try in lab to run it.
AI / ML example
AI / ML: mutate vs rebind
append changes the caller. Rebinding does not.
In AI / ML: Message histories and batch collectors must not mutate by accident.
example.py
Python
def mutates(items):
items.append("x")
def rebinds(items):
items = items + ["x"]
return items
a = ["start"]
mutates(a)
print("mutated:", a)
b = ["start"]
rebinds(b)
print("rebound caller:", b)AI / ML example 2
AI / ML: late-binding trap
Bind each threshold at creation time.
In AI / ML: Score filters built in a loop must bind each threshold.
example.py
Python
broken = [lambda s: s >= t for t in (0.5, 0.7, 0.9)]
fixed = [lambda s, t=t: s >= t for t in (0.5, 0.7, 0.9)]
print([f(0.8) for f in broken])
print([f(0.8) for f in fixed])Takeaways
- 1Names resolve Local, Enclosing, Global, Built-in.
- 2Mutable arguments can change the caller's data.
- 3Closures capture variables; bind loop values with defaults.