All Courses
Advanced Python

Python Closures

Functions Are Objects

Functions in Python are objects with a type of function. This means they can be: - Stored in variables - Passed as arguments to other functions - Returned from functions

def foo(x):
    print(x)

print(type(foo))   # <class 'function'>

def call_func(func, x):
    func(x)

call_func(foo, 5)   # 5

Nested Functions

A function defined inside another function is called an inner or nested function. It is local to the enclosing function's scope.

def outer(x):          # outer / enclosing function
    def inner(y):      # inner / nested function
        print(x + y)
    return inner

Terminology

Term Refers to
Outer / Enclosing function The function that contains another function
Inner / Nested function The function defined inside another function
Free variable A variable used inside the inner function but defined in the enclosing function

Accessing the inner function

The inner function cannot be called by name from outside — only via the return value:

func = outer(5)   # returns the inner function with x=5 already set
func(6)           # 11  (5 + 6)
func(3)           # 8   (5 + 3)

inner(3)          # ❌ NameError — not accessible directly

Each call creates an independent function

func1 = outer(5)
func2 = outer(1)

func1(1)   # 6
func2(1)   # 2  — different x, different behaviour

What is a Closure?

A closure is an inner function that: 1. Accesses a free variable from its enclosing function 2. Can still use that variable after the enclosing function has finished executing

def outer(x):
    def inner():
        print(x)     # x is a free variable — defined in outer, used in inner
    return inner

func = outer(5)   # outer finishes executing here
func()            # 5  — x is still accessible inside the closure

The outer function has "closed" — yet the inner function still has access to what was passed to it. This is why it's called a closure.


Practical Example — Collection (Mutable Free Variable)

Closures work naturally with mutable objects (lists, dicts) because you modify them in place — no reassignment needed:

def collection():
    lst = []                       # free variable (mutable)

    def add_value(value):
        lst.append(value)          # modifies lst directly
        return lst

    return add_value

add_value = collection()
print(add_value(1))   # [1]
print(add_value(2))   # [1, 2]
print(add_value(3))   # [1, 2, 3]

Each call adds to the same list — the closure preserves state between calls.


The nonlocal Keyword

With immutable types (int, str, etc.) you cannot reassign a free variable directly — Python treats it as a new local variable:

def counter(start):
    count = start

    def increment(value):
        count += value    # ❌ UnboundLocalError — creates a new local 'count'
        return count

    return increment

Fix: use nonlocal to tell Python the variable belongs to the enclosing scope:

def counter(start):
    count = start

    def increment(value):
        nonlocal count        # ✅ now refers to the outer 'count'
        count += value
        return count

    return increment

count = counter(2)
print(count(1))   # 3
print(count(1))   # 4
print(count(1))   # 5

nonlocal rules

  • Can only be used inside a nested function
  • References the closest enclosing scope that has the named variable (not necessarily the outermost)
def outer():
    x = 4
    def inner():
        x = 3
        def inner2():
            nonlocal x    # references inner's x (closest enclosing), not outer's x
            x = 2
        inner2()
        print("inner:", x)    # 2  — inner's x was modified
    inner()
    print("outer:", x)        # 4  — outer's x was NOT modified

Closures vs Classes

Closures can mimic simple class behaviour. Both examples below work equivalently:

Closure version

def collection():
    lst = []
    def add_value(value):
        lst.append(value)
        return lst
    return add_value

Class version

class Collection:
    def __init__(self):
        self.lst = []
    def add_value(self, value):
        self.lst.append(value)
        return self.lst

Use a closure when the logic is simple and you want to avoid the overhead of defining a full class.


Key Takeaways & Recap

Concept Summary
Functions are objects Can be stored, passed, and returned like any other value
Nested function A function defined inside another function; local to the enclosing scope
Free variable A variable used in the inner function but defined in the enclosing function
Closure An inner function that retains access to free variables after the outer function finishes
Mutable free variables Can be modified directly (.append, etc.) — no special keyword needed
Immutable free variables Require nonlocal to reassign; otherwise Python creates a new local variable
nonlocal Declares a variable as belonging to the closest enclosing scope
Use case Preserving state between calls; a lightweight alternative to simple classes