Scope & Closures
The Question Python Always Asks
Every time Python encounters a name — price, name, total — it asks: which variable does this refer to? The answer depends on scope — the region of a program where a name is visible and accessible.
You've already seen a glimpse of this: a variable created inside a function isn't accessible outside it. That's scope at work. This lesson makes the rules explicit.
The LEGB Rule
Python looks up names in a fixed order, moving outward through four layers:
- L — Local: Inside the current function
- E — Enclosing: Inside any outer functions that contain this one
- G — Global: At the top level of the module
- B — Built-in: Python's built-in names (
len,print,range, etc.)
Python searches each layer in order and stops at the first match. If nothing is found anywhere, you get a NameError.
Three functions, three scopes, three different x values — none interfering with the others. Each print() finds the nearest x and stops looking.
Reading a Global Variable
A function can read a global variable without any special syntax:
apply_discount reads discount_rate from the global scope. Python finds it at the G layer and uses it.
Modifying a Global Variable
Reading a global is fine. Modifying one from inside a function requires the global keyword — otherwise Python assumes you're creating a new local variable.
Without global count, the line count += 1 would crash — Python would try to read a local count that hasn't been assigned yet.
In practice, modifying globals from functions is considered poor style — it makes programs hard to reason about, because any function anywhere could change shared state. It's worth knowing global exists, but reaching for it regularly is a sign the code needs restructuring.
Closures — Functions That Remember
Now for something genuinely powerful. What happens when an inner function references a variable from its enclosing function — and that inner function outlives the outer one?
make_multiplier(2) creates and returns the inner multiply function. At this point, make_multiplier has finished — its local scope is gone. And yet double(10) still produces 20. How?
Because multiply closed over the variable factor. It captured a reference to factor from the enclosing scope and carries that reference with it — even after the enclosing function is long gone.
This is a closure: a function bundled together with the variables from the scope in which it was defined.
A Practical Closure
Each call to make_counter() creates an independent counter with its own count. counter and page_counter have completely separate states — they don't share anything.
Notice count is a list containing one number, not a plain integer. This is a subtle necessity: closures can read and mutate the contents of an enclosing variable, but reassigning the variable itself (like count = count + 1) would create a new local variable instead. Wrapping the value in a list sidesteps this. In Python 3, you can use the nonlocal keyword instead:
nonlocal count tells Python: don't create a new local variable — use the count from the enclosing scope. Cleaner than the list workaround.
Why Closures Matter
Closures let you create functions with private, persistent state — without needing classes or global variables. Each closure carries its own memory, invisible to the outside world.
You'll encounter closures constantly in Python — in decorators, in callbacks, in any situation where behaviour needs to be configured at creation time rather than at call time. Understanding them removes a large category of "why does this work?" confusion from more advanced code.
What You Have Learned
Scope rules determine which variable Python uses when a name appears — closures let functions carry those variables across time.
The key ideas:
- Python resolves names using LEGB: Local → Enclosing → Global → Built-in
- Functions can read global variables freely; modifying them requires
global - A closure is an inner function that captures variables from its enclosing scope
- The enclosed variables persist as long as the closure exists
nonlocalallows an inner function to reassign an enclosing variable (not just read it)- Closures enable functions with private, independent state — no classes required
In the next lesson, you'll learn how to handle errors gracefully — writing programs that can fail without crashing.