Skip to content

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.

Scope determines where a name (variable, function) can be accessed. Python resolves names using the LEGB rule, searching four scopes in order:

  1. Local — names assigned inside the current function.
  2. Enclosing — names in any enclosing (outer) function.
  3. Global — names at the top level of the module.
  4. Built-in — names pre-defined by Python (len, print, range, …).

Diagram:

diagram LEGB rule mermaid
Order Python searches scopes to resolve a name

The following example shows all four levels being resolved:

legb.py
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:

command
C:\Users\Your Name> python legb.py
local
enclosing
global
<built-in function len>

A variable assigned inside a function is local to that function and disappears when the function returns. It cannot be accessed from outside.

local.py
def greet():
    message = "Hello"   # local variable
    print(message)
 
greet()
print(message)          # NameError: 'message' is not defined

Output:

command
C:\Users\Your Name> python local.py
Hello
Traceback (most recent call last):
NameError: name 'message' is not defined

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.

global_keyword.py
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:

command
C:\Users\Your Name> python global_keyword.py
2

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.

nonlocal_keyword.py
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:

command
C:\Users\Your Name> python nonlocal_keyword.py
1
2
3

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:

  1. There is a nested (inner) function.
  2. The inner function refers to a variable from the enclosing function.
  3. The enclosing function returns the inner function.
closure.py
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))   # 15

Output:

command
C:\Users\Your Name> python closure.py
10
15

Even 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:

diagram closure mermaid
How a closure captures an enclosing variable

You can see the values a closure has captured through its __closure__ attribute.

inspect_closure.py
double = make_multiplier(2)
print(double.__closure__[0].cell_contents)

Output:

command
C:\Users\Your Name> python inspect_closure.py
2
  • 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.

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!


pch.coffeeTagline

pch.coffeeCta

pch.feedbackHeading

pch.feedbackSubheading