Closures and Scope (LEGB) in Python
Mastering Closures and Scope in Python: A Comprehensive Guide
Every variable in Python lives in a scope — a region of the program where that name is visible. Understanding scope explains why some variables are accessible inside a function and others are not, and why changing a variable inside a function sometimes affects the outside and sometimes does not. Closures build on scope: they let an inner function remember variables from the function that created it. In this guide, we’ll explore the LEGB rule, the globalglobal and nonlocalnonlocal keywords, and closures.
What is Scope?
Scope determines where a name (variable, function) can be accessed. Python resolves names using the LEGB rule, searching four scopes in order:
- Local — names assigned inside the current function.
- Enclosing — names in any enclosing (outer) function.
- Global — names at the top level of the module.
- Built-in — names pre-defined by Python (
lenlen,printprint,rangerange, …).
Diagram:
graph TD
A[Name used] --> B[Local scope?]
B -->|Found| Z[Use it]
B -->|Not found| C[Enclosing scope?]
C -->|Found| Z
C -->|Not found| D[Global scope?]
D -->|Found| Z
D -->|Not found| E[Built-in scope?]
E -->|Found| Z
E -->|Not found| F[NameError]
The following example shows all four levels being resolved:
x = "global" # Global scope
def outer():
y = "enclosing" # Enclosing scope
def inner():
z = "local" # Local scope
print(z) # found in Local
print(y) # found in Enclosing
print(x) # found in Global
print(len) # found in Built-in
inner()
outer()x = "global" # Global scope
def outer():
y = "enclosing" # Enclosing scope
def inner():
z = "local" # Local scope
print(z) # found in Local
print(y) # found in Enclosing
print(x) # found in Global
print(len) # found in Built-in
inner()
outer()Output:
C:\Users\Your Name> python legb.py
local
enclosing
global
<built-in function len>C:\Users\Your Name> python legb.py
local
enclosing
global
<built-in function len>Local Scope
A variable assigned inside a function is local to that function and disappears when the function returns. It cannot be accessed from outside.
def greet():
message = "Hello" # local variable
print(message)
greet()
print(message) # NameError: 'message' is not defineddef greet():
message = "Hello" # local variable
print(message)
greet()
print(message) # NameError: 'message' is not definedOutput:
C:\Users\Your Name> python local.py
Hello
Traceback (most recent call last):
NameError: name 'message' is not definedC:\Users\Your Name> python local.py
Hello
Traceback (most recent call last):
NameError: name 'message' is not definedGlobal Scope and the global Keyword
By default, a function can read a global variable but assigning to a name inside a function creates a new local variable. To modify a global variable from inside a function, use the globalglobal keyword.
count = 0
def increment_wrong():
count = count + 1 # ERROR: count is treated as local
def increment_right():
global count # tell Python to use the global 'count'
count = count + 1
increment_right()
increment_right()
print(count)count = 0
def increment_wrong():
count = count + 1 # ERROR: count is treated as local
def increment_right():
global count # tell Python to use the global 'count'
count = count + 1
increment_right()
increment_right()
print(count)Output:
C:\Users\Your Name> python global_keyword.py
2C:\Users\Your Name> python global_keyword.py
2Enclosing Scope and the nonlocal Keyword
When you have a function defined inside another function, the inner function can read the outer function’s variables. To reassign an enclosing variable, use the nonlocalnonlocal keyword.
def counter():
count = 0
def increment():
nonlocal count # refer to 'count' in the enclosing scope
count += 1
return count
print(increment())
print(increment())
print(increment())
counter()def counter():
count = 0
def increment():
nonlocal count # refer to 'count' in the enclosing scope
count += 1
return count
print(increment())
print(increment())
print(increment())
counter()Output:
C:\Users\Your Name> python nonlocal_keyword.py
1
2
3C:\Users\Your Name> python nonlocal_keyword.py
1
2
3What is a Closure?
A closure is a function that remembers the variables from its enclosing scope even after that outer function has finished running. Three conditions make a closure:
- There is a nested (inner) function.
- The inner function refers to a variable from the enclosing function.
- The enclosing function returns the inner function.
def make_multiplier(factor):
# 'factor' is captured by the inner function
def multiplier(number):
return number * factor
return multiplier # return the function, do not call it
double = make_multiplier(2)
triple = make_multiplier(3)
print(double(5)) # 10
print(triple(5)) # 15def make_multiplier(factor):
# 'factor' is captured by the inner function
def multiplier(number):
return number * factor
return multiplier # return the function, do not call it
double = make_multiplier(2)
triple = make_multiplier(3)
print(double(5)) # 10
print(triple(5)) # 15Output:
C:\Users\Your Name> python closure.py
10
15C:\Users\Your Name> python closure.py
10
15Even though make_multipliermake_multiplier has already returned, doubledouble still remembers factor = 2factor = 2 and tripletriple still remembers factor = 3factor = 3. Each closure carries its own captured value.
Diagram:
graph TD
A[Call make_multiplier 2] --> B[factor = 2 captured]
B --> C[Return inner function 'double']
C --> D[make_multiplier finishes]
D --> E[double still remembers factor = 2]
E --> F[double 5 returns 10]
Inspecting a Closure
You can see the values a closure has captured through its __closure____closure__ attribute.
double = make_multiplier(2)
print(double.__closure__[0].cell_contents)double = make_multiplier(2)
print(double.__closure__[0].cell_contents)Output:
C:\Users\Your Name> python inspect_closure.py
2C:\Users\Your Name> python inspect_closure.py
2Why Closures Matter
- They let you build function factories (like
make_multipliermake_multiplier) that generate customised functions. - They are the foundation of decorators.
- They keep data private inside a function without needing a class.
Conclusion
Scope controls where names are visible, and Python resolves them with the LEGB rule: Local, then Enclosing, then Global, then Built-in. Use globalglobal to reassign a module-level variable and nonlocalnonlocal to reassign an enclosing function’s variable. Closures take this further — an inner function returned from an outer one remembers the variables it captured, enabling powerful patterns like function factories and decorators. For more hands-on examples and in-depth tutorials, explore our resources on Python Central Hub!
Try it: Closures and Scope Exercises
Exercise 1 – The global Keyword
Exercise 2 – The nonlocal Keyword
Exercise 3 – Build a Closure
If this helped you, consider buying me a coffee ☕
Buy me a coffeeWas this page helpful?
Let us know how we did
