Closures and Scope (LEGB) in Python
Mastering Closures and Scope in Python: A Comprehensive Guide
Section titled “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 global and nonlocal keywords, and closures.
What is Scope?
Section titled “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 (
len,print,range, …).
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()Output:
C:\Users\Your Name> python legb.py
local
enclosing
global
<built-in function len>Local Scope
Section titled “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 definedOutput:
C:\Users\Your Name> python local.py
Hello
Traceback (most recent call last):
NameError: name 'message' is not definedGlobal Scope and the global Keyword
Section titled “Global 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 global 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)Output:
C:\Users\Your Name> python global_keyword.py
2Enclosing Scope and the nonlocal Keyword
Section titled “Enclosing 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 nonlocal 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()Output:
C:\Users\Your Name> python nonlocal_keyword.py
1
2
3What is a Closure?
Section titled “What 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)) # 15Output:
C:\Users\Your Name> python closure.py
10
15Even though make_multiplier has already returned, double still remembers factor = 2 and triple still remembers factor = 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
Section titled “Inspecting a Closure”You can see the values a closure has captured through its __closure__ attribute.
double = make_multiplier(2)
print(double.__closure__[0].cell_contents)Output:
C:\Users\Your Name> python inspect_closure.py
2Why Closures Matter
Section titled “Why Closures Matter”- They let you build function factories (like
make_multiplier) that generate customised functions. - They are the foundation of decorators.
- They keep data private inside a function without needing a class.
Conclusion
Section titled “Conclusion”Scope controls where names are visible, and Python resolves them with the LEGB rule: Local, then Enclosing, then Global, then Built-in. Use global to reassign a module-level variable and nonlocal 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
Section titled “Try it: Closures and Scope Exercises”Exercise 1 – The global Keyword
Section titled “Exercise 1 – The global Keyword”Exercise 2 – The nonlocal Keyword
Section titled “Exercise 2 – The nonlocal Keyword”Exercise 3 – Build a Closure
Section titled “Exercise 3 – Build a Closure”pch.coffeeTagline
pch.coffeeCtapch.feedbackHeading
pch.feedbackSubheading