Namespaces and Scope
Introduction
In 2.1 we saw that names live in frames while the objects they point to live in the heap. A running call keeps its own local names in its frame; the module keeps its names at the top level. Each such collection of names has a proper title: a namespace. This page answers the two questions that follow immediately. How many namespaces are in play at once? And when you write a bare name like x, which namespace does Python look in to find it? The answer to the second is a four-letter rule — LEGB — that you will use to reason about almost every function you read.
As always, the code here is runnable — press Run, change it, run it again.
1. A namespace is a dictionary of names Slides
A namespace is exactly what 2.1's frames hinted at: a mapping from names to the objects they refer to — literally a Python dictionary underneath. Writing x = 5 adds or updates the entry "x" → <the object 5> in whichever namespace you are currently in. Nothing more mysterious than that.
And Python keeps several namespaces at once. You have already met two of them: the global namespace (the module's top-level names, which globals() returns) and a local namespace (the names inside the function call that is running, which locals() returns).
The example below shows the two are genuinely separate dictionaries.
Example: globals() and locals()
Key concept: namespace
A namespace is a mapping from names to objects (a dictionary). Python maintains several at once — built-in, global, enclosing, and local — and every name you use is resolved by looking through them.
2. Functions can be defined inside functions
A function body is just ordinary code, and nothing stops that code from containing another def. A function defined inside another is a nested function — and it can do something new: it can see the names of the function that encloses it.
The example below defines inner inside outer. When inner runs, it reads message — a local name belonging to outer — even though message is not one of inner's own locals.
Example: an inner function sees the outer's names
Two things to notice. First, writing def inner only creates inner each time outer runs; like any function it does nothing until it is called, which is why we then write inner(). Second, inner exists only while outer is running — it is just a local name of outer, born and discarded with that call.
The surrounding scope that inner can see — outer's namespace — is called the enclosing scope. It is the "E" in the LEGB rule we are about to meet, and the whole reason nonlocal exists. That is all we need for now; in 2.3 we put nested functions to real work, passing them around and returning them.
3. The four namespaces
At any moment, up to four kinds of namespace can be in scope:
- Local — the names inside the function call running right now: its frame. Created when the call starts, discarded when it returns.
- Enclosing — the local names of an outer function, as seen by a nested function defined inside it (the case we just met in §2). That outer namespace is the inner function's enclosing one. We put nested functions to real work in 2.3.
- Global — the names defined at the top level of your module. One per module, created when the module starts, alive for the whole program.
- Built-in — the names Python pre-defines and makes available everywhere:
print,len,range,max,True,None, and so on. They live in thebuiltinsmodule.
The example below confirms that the built-in names really do live in a module of their own.
Example: the built-in namespace
4. Scope and the LEGB rule Slides
Scope is the region of a program where a name is reachable without any qualification. Each namespace has a scope. When you use a bare name, Python searches the namespaces in a fixed order and stops at the first match:
Local → Enclosing → Global → Built-in
This is the LEGB rule. If the name turns up in none of the four, Python raises a NameError.
The four scopes nest, one inside the next: a name search starts in the innermost (Local) and works its way out to the Builtins. The picture below is the map. On the left are the scope names; on the right, the code that lives in each. It also previews the two escape hatches from §6 — global lets the innermost function rebind a name all the way out in the module scope, and nonlocal lets it rebind a name in the enclosing function.
The example below puts three x's in three different scopes; each print(x) finds the nearest one in the search order.
Example: LEGB in action
The picture below freezes the moment inner() is running. There are three different names, all spelled x, each in its own namespace and each pointing at its own string in the heap. inner's lookup finds the local x first and never consults the others. (The built-in namespace sits one step further out still, and is not drawn.)
When a name is not found locally, the search simply continues outward — which is why a function can read a global without any ceremony.
Example: lookup falls through to the global
5. Shadowing: when one name hides another Slides
Bind a name in an inner scope that also exists in an outer one, and the inner binding shadows — hides — the outer one for the rest of that scope. Usually this is harmless and even useful (every function is free to have its own i or result). Occasionally it is a trap, most painfully when you shadow a built-in.
Example: shadowing the built-in max
Pitfall: don't name things after built-ins
It is easy to reach for list, dict, sum, max, id, type, or str as
variable names — and the moment you do, you lose the built-in for the rest of
that scope. The symptom is baffling: list(...) suddenly raises
TypeError: 'list' object is not callable, because list is now your data,
not the type. Pick another name (items, total, kind, text).
6. Assigning inside a function: local by default
Here is the rule that explains most scope surprises: assigning to a name anywhere in a function makes that name local to the whole function — even if a global of the same name exists, and even on lines before the assignment. Reading a name is fine (LEGB walks outward and finds the global); but the moment you assign, you have declared a local.
So reading a global from inside a function just works.
Example: reading a global
But assigning to that same name makes it local throughout the function — which produces a famous error if you also read it first.
Pitfall: UnboundLocalError
Because counter is assigned inside bump, it is local for the whole
function — so the right-hand side counter + 1 reads a local that has no value
yet. Python does not fall back to the global here. The fix is to be explicit
about your intent, with global or nonlocal.
To rebind a global from inside a function, declare it with global. To rebind a name from an enclosing function, declare it with nonlocal. Those two keywords are the fix for UnboundLocalError, and they are the two ways to build the same small thing: a counter that survives between calls.
Start with the global version. count_calls needs to remember, across separate calls, how many times it has run — and a local would be rebuilt and discarded on every call, so the tally has to live outside.
Example: a counter in the global namespace
It works, but look at what it costs: the tally is a module-level variable that anything can read or overwrite, and the function only makes sense alongside it. The counter is not really part of the world; it belongs to the function.
nonlocal lets us say exactly that. Put the tally in an enclosing function, and the inner function can rebind it — so the count lives in the outer call's frame, reachable by counter and by nobody else.
Example: the same counter, bound to an inner function
def make_counter():
calls = 0 # lives in make_counter's frame
def counter():
nonlocal calls # 'calls' refers to make_counter's local
calls += 1
print(f"calling {calls} times")
return counter
c = make_counter()
c() # calling 1 times
c() # calling 2 times
d = make_counter() # a second, independent counter
d() # calling 1 times
print("calls" in globals()) # False — nothing leaked into the global namespace
Two things are worth noticing. The name calls never appears in the global namespace, so the counter is genuinely private. And c and d count independently, because each call to make_counter created its own frame with its own calls. That is the difference between one shared global and a value bound to a particular function — and it is the whole reason nonlocal exists.
This pattern has a name, closure, and 2.3 develops it properly. There we also extend this very counter so that it can wrap another function and pass along however many arguments that function needs.
6.1 Back to the function factory
We can now repair the puzzle left open in 2.1 §7. Recall the factory: five functions built in a loop, every one of them returning 4, because f had no i of its own and looked the name up — out in the global namespace — only when it was called.
Everything in this page explains that. i was not local to f (nothing in f assigned to it), and it was not enclosing either (the loop is not a function), so LEGB walked all the way out to the global i, whose value at call time was 4.
The honest fix is to give each function an enclosing frame of its own, by building it inside a function instead of inside a bare loop:
Example: the factory, repaired
Nothing here is a trick. Each call to make_f creates a frame holding that iteration's i, and the f it returns keeps hold of that frame's name. Five calls, five frames, five independent values — exactly as with c and d above.
Pitfall: reach for return, not global
global works, but a function that quietly rewrites module-level variables is
hard to follow and easy to break. The clearer habit is to pass values in as
arguments and hand results back with return, keeping each function
self-contained. Save global/nonlocal for the few cases that genuinely need
shared, mutable state.
7. Why have scopes at all? Slides
Deep dive: what scopes buy you
Scopes are not red tape — they earn their keep. Encapsulation: because a
function's locals are invisible outside it, you can reuse plain names like i,
x, or result in every function without fear of collision. Information
hiding: a function's internal names stay private, so callers depend only on
what it returns, not on how it works. Reclaiming memory: because the local
namespace vanishes when a call returns, the objects that only it referred to
lose their last reference and can be freed immediately — the reference-counting
cleanup we mentioned in 2.1. Small, well-scoped functions are easier to read,
test, and reason about precisely because their names cannot leak.
Summary
Python resolves every bare name through a stack of namespaces:
| Namespace | Where it applies | Inspect with | Lifetime |
|---|---|---|---|
| Local | inside one running call | locals() |
that call |
| Enclosing | inside a nested function | — | while the outer call runs |
| Global | the whole module | globals() |
the whole program |
| Built-in | everywhere, unless shadowed | dir(builtins) |
the whole program |
A name is looked up L → E → G → B, stopping at the first match (or a NameError). Assigning to a name makes it local for the entire function, so reach for global or nonlocal only when you truly mean to rebind an outer name — and prefer passing arguments and returning results instead. Shadowing is fine for your own names but dangerous for built-ins.
Next, 2.3 Functions Are First-Class Objects takes the nested functions we just leaned on and makes them the main event: passing functions as arguments, returning them, and the closures that let an inner function remember its enclosing scope.