A closure is a function that remembers the variables from its enclosing scope, even after that scope has finished executing. To understand closures, we first need to know that functions in Python are first-class objects — we can pass them around, return them from other functions, and assign them to variables.
First-Class Functions
def greet(name):
return f"Hello, {name}!"
# Assign a function to a variable
say_hello = greet
say_hello("Manish") # "Hello, Manish!"
# Pass a function as an argument
def call_twice(func, arg):
return func(arg) + " " + func(arg)
call_twice(greet, "World") # "Hello, World! Hello, World!"
Inner Functions
We can define functions inside other functions. The inner function has access to the outer function’s variables.
def outer():
message = "Hello from outer"
def inner():
print(message) # can access outer's variable
inner()
outer() # "Hello from outer"
What Makes It a Closure?
A closure happens when we return the inner function, and it keeps a reference to the enclosing variables even after the outer function has finished running.
def make_multiplier(multiplier):
def multiply(x):
return x * multiplier # remembers multiplier from outer scope
return multiply # return the function, don't call it
double = make_multiplier(2)
triple = make_multiplier(3)
double(5) # 10 — multiplier=2 is remembered
triple(5) # 15 — multiplier=3 is remembered
Even though make_multiplier has finished executing, the returned multiply function still has access to the multiplier variable. That’s a closure.
The nonlocal Keyword
By default, an inner function can read variables from the enclosing scope but can’t reassign them. If we try, Python creates a new local variable instead. The nonlocal keyword lets us modify the enclosing variable.
def counter():
count = 0
def increment():
nonlocal count # without this, we'd get an UnboundLocalError
count += 1
return count
return increment
tick = counter()
tick() # 1
tick() # 2
tick() # 3 — count persists between calls
Without nonlocal, Python would think count += 1 is trying to read a local variable count before it’s been assigned.
Practical Uses
Factory Functions
Closures are great for creating specialized functions.
def make_greeter(greeting):
def greet(name):
return f"{greeting}, {name}!"
return greet
casual = make_greeter("Hey")
formal = make_greeter("Good evening")
casual("Manish") # "Hey, Manish!"
formal("Manish") # "Good evening, Manish!"
Data Hiding
Closures can act like lightweight objects, encapsulating state without a class.
def bank_account(initial_balance):
balance = initial_balance
def transact(amount):
nonlocal balance
balance += amount
return balance
return transact
account = bank_account(100)
account(50) # 150 (deposit)
account(-30) # 120 (withdrawal)
In simple language, a closure is an inner function that carries a little backpack of variables from its parent function. Even after the parent is gone, the backpack stays.