Understanding how Python finds and ties names to objects is crucial for writing correct, maintainable code. In this chapter, you’ll learn about variable scope (where names are visible) and binding (how names reference values), following Python’s LEGB rule. You’ll see how local, global, enclosing, and built-in scopes interact, and how to use the global and nonlocal statements to modify bindings.
1. Introduction to Scope and Binding
Every time you assign a value to a name, you bind that name to an object in memory. The scope of a name is the region of your code where that binding is visible. Python determines which object a name refers to by searching scopes in a specific order.
Properly managing scope prevents unintended side-effects, helps avoid name clashes, and clarifies which variables your functions actually use.
2. The LEGB Rule
Python resolves names by searching four nested scopes in order:
- Local: Names defined inside the current function.
- Enclosing: Names in any enclosing function’s scope (for nested functions).
- Global: Names at the module level.
- Built-in: Names pre-defined in Python’s built-in namespace (
print,len, etc.).
When you reference a name, Python checks each scope in that order and stops at the first match.
3. Local Scope
Names assigned inside a function are local to that function call. They exist only while the function is executing:
def greet():
msg = "Hello, World!" # msg is local
print(msg)
greet()
# print(msg) # NameError: msg is not defined
Each call to greet() creates a fresh, independent msg.
4. Global Scope
Names defined at the top level of a module—or explicitly declared global—reside in the global scope and are accessible throughout that module:
user = "Alice" # global variable
def show_user():
print(user) # reads the global 'user'
show_user() # Alice
Accessing a global name for reading requires no special keyword. However, assigning to that name inside a function would create a new local variable unless you use global.
5. Enclosing (Nonlocal) Scope
In nested functions, the enclosing scope refers to the namespace of the outer function. Names there aren’t local, but they aren’t global either. To modify them, use nonlocal:
def outer():
count = 0 # enclosing variable
def inner():
nonlocal count # bind to outer’s 'count'
count += 1
print(count)
inner() # 1
inner() # 2
outer()
Without nonlocal, assigning to count inside inner() would create a new local count, leaving the outer count unchanged.
6. Built-in Scope
The built-in scope holds Python’s standard names (e.g., len, range, Exception). It’s searched last. You can shadow built-ins, but doing so can lead to confusing bugs:
len = 5
print(len) # 5
# print(len([1,2,3])) # TypeError: 'int' object is not callable
Avoid reassigning built-ins to keep code clear and safe.
7. Namespaces and Binding
A namespace is a mapping of names to objects. Each scope has its own namespace:
- Local namespace: Function parameters and local variables.
- Global namespace: Module-level names.
- Built-in namespace: Python’s built-ins.
When you write x = 10, Python binds the name x into the current namespace. When you reference x, Python looks up the value in the appropriate namespace, following LEGB.
8. Modifying Bindings with global and nonlocal
global nameinside a function tells Python to bind assignments to the module-levelname.nonlocal nametells Python to bind assignments to the nearest enclosing function’sname.
count = 0
def increment():
global count # refer to module’s 'count'
count += 1
increment()
print(count) # 1
Use these statements sparingly—relying too heavily on globals can make code hard to reason about.
9. Common Pitfalls
- UnboundLocalError: Occurs when you assign to a name in a function without declaring it
globalornonlocal, but also reference it before assignment. - Shadowing: Defining a local name with the same name as a global or built-in hides the outer variable.
- Mutable Default Arguments: Using a mutable global or default list can lead to unexpected shared state across calls.
Always be explicit about where your variables live and how they’re modified.
10. Mini Project: Scope Demonstrator
Build a script that illustrates each scope and binding rule interactively:
# scope_demo.py
from typing import Callable
def demo(local, enclosing, use_global=False, use_nonlocal=False):
print("Inside demo:")
print(" Local:", local)
print(" Enclosing:", enclosing)
print(" Global before:", GLOBAL_VAR)
if use_global:
global GLOBAL_VAR
GLOBAL_VAR = "modified global"
def inner():
if use_nonlocal:
nonlocal enclosing
enclosing = "modified enclosing"
print(" In inner, enclosing:", enclosing)
inner()
print(" Global after:", GLOBAL_VAR)
print()
# Global variable
GLOBAL_VAR = "original global"
# Enclosing variable holder
encloser = "original enclosing"
# Run demonstrations
demo(local="local value", enclosing=encloser)
demo(local="local value", enclosing=encloser, use_global=True)
demo(local="local value", enclosing=encloser, use_nonlocal=True)
Run python scope_demo.py to see how each flag affects bindings across scopes.
11. Conclusion
Mastering variable scope and binding is key to building reliable Python applications. By following the LEGB rule, using global and nonlocal judiciously, and keeping namespaces clear, you’ll prevent hard-to-find bugs and write code that’s easier to maintain and understand. Happy coding!
Discover more from Affordable Tutor
Subscribe to get the latest posts sent to your email.


