Namespaces and scopes are the systems Python uses to track which names exist and where they can be accessed. A namespace is a mapping from names to objects. A scope is a region of code where a namespace is directly accessible.
The article on attribute lookup in Python covered how Python resolves attributes on objects. This article covers name resolution in general: how Python decides what a bare name like x refers to, how functions create local scopes, and how closures capture variables from enclosing functions.
Understanding namespaces and scopes is essential for debugging NameError and UnboundLocalError, writing correct closures in loops, and using the global and nonlocal statements effectively.
The LEGB rule
Python resolves names by searching four scopes in a fixed order. This is called the LEGB rule, short for Local, Enclosing, Global, Built-in.
The local scope is the current function. Names defined inside the function by assignment or as parameters are local. The enclosing scope applies to nested functions and contains names from outer functions.
The global scope is the module level. Names defined at the top of a module or declared global inside a function are global. The built-in scope contains names like print, len, and range that are always available.
x = "global"
def outer():
x = "enclosing"
def inner():
x = "local"
print(x)
inner()
outer()The outer function creates the enclosing scope. The inner function creates the local scope and prints x from that innermost scope.
localRemove the local assignment and Python finds the enclosing version. Remove that too and Python reaches the global. This cascading resolution is the LEGB rule in action.
How namespaces are stored
Every scope in Python is backed by a dictionary. The local and global scopes can be inspected with the locals and globals built-in functions.
top_level = "module scope"
def show_namespaces():
local_var = "inside"
print("Locals:", list(locals().keys()))
print("Globals has top_level:", "top_level" in globals())
print("Globals has print:", "print" in globals())
show_namespaces()The locals call returns the current function namespace dictionary with all local variables. The globals call returns the module-level namespace, which holds top_level but not print, since print lives in the separate built-in scope rather than the global one.
Locals: ['local_var']
Globals has top_level: True
Globals has print: FalseObject namespaces are stored in the dict attribute. When you access obj.x, Python looks in type(obj).dict for descriptors and in obj.dict for instance attributes.
class Point:
origin = (0, 0)
p = Point()
p.x = 10
print(Point.__dict__.keys())
print(p.__dict__)The class dict contains origin and various metadata methods. The instance dict contains only the x attribute that was set directly on the instance.
dict_keys(['__module__', '__firstlineno__', 'origin', '__static_attributes__', '__dict__', '__weakref__', '__doc__'])
{'x': 10}How closures capture variables
A closure is a nested function that references variables from an enclosing function after the enclosing function has returned. Python stores these captured variables in cell objects rather than in the enclosing function's local namespace.
def make_counter():
count = 0
def counter():
nonlocal count
count += 1
return count
return counter
c1 = make_counter()
print(c1())
print(c1())
print(c1())The counter function references count from make_counter. The nonlocal declaration is required because the function assigns to count. Without nonlocal, Python would treat count as a local variable.
1
2
3Each call to make_counter creates a new cell for count. Multiple closures from the same factory do not share state.
c2 = make_counter()
print(c1())
print(c2())The two counters are independent because each call to make_counter creates a fresh count cell and a new closure that references it.
4
1You can inspect closure cells through the closure attribute on the function object. Each cell contains the current value of a captured variable.
The global and nonlocal statements
By default, assigning to a name inside a function creates a local variable. The global statement tells Python to assign to a global variable instead. The nonlocal statement tells Python to assign to a variable in an enclosing scope.
total = 0
def accumulate(value):
global total
total += value
accumulate(10)
accumulate(5)
print(total)Without the global declaration, total += value would create a local variable and fail because a local named total would not have an initial value.
15Nonlocal is required when a nested function assigns to an enclosing variable. Reading an enclosing variable does not require nonlocal. Only assignment requires the declaration.
def outer():
message = "hello"
def reader():
return message
def writer():
nonlocal message
message = "updated"
writer()
return reader()
print(outer())The reader function reads message without nonlocal. The writer function assigns to it and must declare nonlocal to avoid creating a local shadow.
updatedThe article on Python memory management covers how closure cells affect object lifetimes and garbage collection.
Rune AI
Key Insights
- Python resolves names using the LEGB order: Local, Enclosing, Global, Built-in.
- The global and nonlocal statements change where assignments store their target.
- Object namespaces are stored in the dict attribute as regular dictionaries.
- Closures use cell objects to share variables between enclosing and nested functions.
- The locals() and globals() functions return the current scope dictionaries for inspection.
Frequently Asked Questions
What is the LEGB rule in Python?
How do Python closures capture variables?
Conclusion
Python's namespace and scope system is the mechanism behind every name lookup in the language. The LEGB rule provides a predictable resolution order. The dict attribute on objects stores instance namespaces. Closure cells capture enclosing variables by reference. Understanding these internals makes you better at debugging NameErrors, writing correct closures, and reasoning about variable lifetimes.
More in this topic
How to Test Python Code
Learn what testing means in Python, the core ideas behind automated tests, and the built-in and third-party tools that make testing practical.
Write Your First Python Unit Test
Write and run your first Python unit test step by step, using unittest to check a small function and understand the test output.
The Factory Pattern in Python
Learn how to use the factory pattern in Python to encapsulate object creation, making your code more flexible and easier to extend.