Nested Functions
Overview
Functions can be defined inside other functions to avoid repeating code. With nested functions, the inner function can use variables from the outer function.
In this example, the function outer_fn defines an inner function called inner_fn that adds x (from outer_fn) to y (passed to inner_fn).
def outer_fn(x):
def inner_fn(y):
return x + y
return inner_fn
add_num = outer_fn(5)
print(add_num(3))
Output:
8
Explanation:
-
Python enters
outer_fnwithx = 5when calling:add_num = outer_fn(5)Notes:
- The inner function
inner_fn(y)is defined but not executed. inner_fn(y)remembersx = 5from the outer function (closure).outer_fnreturnsinner_fn.- The variable
add_numnow points to this inner function. - Calling
add_num(y)will executeinner_fnwithyas input.
- The inner function
-
When this line runs:
print(add_num(3))Python passes
3asyto the inner function stored inadd_num. It calculatesx + y➔5 + 3 = 8.
This shows that add_num is a function, x is captured in the closure, and y is provided when the returned function is called.
Closures
Outer functions can return inner functions to create customized behavior, and the inner function remembers variables from the outer function.
This is called a closure because the inner function retains access to the outer function’s variables even after the outer function has finished executing.
- Values passed to a nested function are stored in the closure
- Deleting or overwriting the original variable does not affect the closure
This allows functions to maintain state independently of the global or parent scope
Examples
Simple foo
In the example below, foo() defines a nested function bar() that prints a. When we return bar() and assign it to func, calling func() still knows the value of a because of the closure.
def foo():
a = 5
def bar():
print(a)
return bar
func = foo()
# Calls bar() and prints value of "a"
func()
# Access the closure value
print(func.__closure__[0].cell_contents)
Output:
5
5
The closure for func has one variable, which you can view by running:
len(func.__closure__) ## Output: 1
Even if the original variable goes out of scope, the value is preserved in the function’s closure.
raise_val
In this example, raise_val returns the inner function inner_fn that raises a number to the power n. square and cube are functions created by raise_val.
def raise_val(n):
def inner_fn(x):
return x ** n
return inner_fn
square = raise_val(2)
cube = raise_val(3)
print(square(4)) # Output: 16
print(cube(2)) # Output: Output: 8
Explanation: The explanation below is for square but works the same way for cube.
-
Python enters
raise_valwithn = 2when calling:square = raise_val(2)Notes:
- The
inner_fn(x)is defined but not executed. - The
inner_fn(x)remembersn = 2fromraise_val(this is the closure). - The
raise_valreturnsinner_fn - The variable
squarenow points to this function. - Now,
square(x)callsinner_fnwithxas input.
- The
-
When this line is ran:
print(square(4))Python passes
4asxto the inner function stored insquare. It calculatesx ** n➔4 ** 2 = 16.
This shows that square and cube are functions, n is captured by the closure, and x is provided when the returned function is called.
Keeping the values safe
In this example, the function retrieve_new_func accepts another function as an argument. Inside it, a nested function called in_func is defined. This nested function calls the function (func_x') that was originally passed into retrieve_new_func`.
When retrieve_new_func is executed, it returns the nested function in_func. However, in_func still remembers the original function (func_x) that was passed in.
That remembered variable (func_x) becomes part of the closure.
def retrieve_new_func(func_x):
def in_func():
func_x()
return in_func
def special_func():
print('You are running special_func()')
new_func = retrieve_new_func(special_func)
# Update special_func() to print "hello"
def special_func():
print("hello")
new_func()
Output:
You are running special_func()
Even if we later changespecial_func, the returned function (new_func) will still behave the same way because new_func already captured the original special_func object when it was created.
For example, if we delete special_func, the global name disappear but the closure still has the reference, we we can still call new_func without any issues.
def retrieve_new_func(func):
def in_func():
func()
return in_func
def special_func():
print('You are running special_func()')
new_func = retrieve_new_func(special_func)
# Delete special_func
del(special_func)
new_func()
Output:
You are running special_func()
The closure stores the function object that was passed in, not the variable name special_func.
Finally, even if special_func is overwritten with the new function, calling new_func still produces the original message.
def retrieve_new_func(func):
def in_func():
func()
return in_func
def special_func():
print('You are running special_func()')
new_func = retrieve_new_func(special_func)
# Overwrite `special_func` with the new function
special_func = retrieve_new_func(special_func)
new_func()
Output:
You are running special_func()
Important: Overwriting special_func does not create a loop, because the in_func returned by retrieve_new_func remembers the function that existed at the time it was created. It stores a reference to that original function in its closure, not the new in_func that the name special_func points to.
Using nonlocal
nonlocal lets inner functions modify variables in outer functions. It works like global but only for enclosing function variables.
In this example, counter defines inc that increases n from counter each time it is called.
def counter():
n = 0
def inc():
nonlocal n
n += 1
return n
return inc
c = counter()
print(c()) # 1
print(c()) # 2
Explanation:
-
Python enters
counter()and setsn = 0. -
The inner function
inc()is defined but not executed yet. -
counter()returnsinc, socnow points toinc. -
When
c()is called the first time:- Python runs
inc()with access tonfrom the enclosing scope. nis increased by 1 (n = 1) and returned.
infoWhen you use
nonlocal ninsideinc, Python looks up one level to the enclosing scope (the outer functioncounter) to findn. - Python runs
-
When
c()is called the second time:inc()runs again, remembering the previousn = 1.nis increased by 1 (n = 2) and returned.
Scopes (LEGB Rule)
Scope determines which variables Python can access at different points in your code.
Python uses a set of rules called LEGB to figure out which variable you mean.
Local ➔ Enclosing ➔ Global ➔ Built-in.

Assigning without global or nonlocal affects only local scope.
In this example, outer_fn defines x and inner_fn prints it. Python finds x in the enclosing scope (the outer function outer_fn).
x = 10
def outer_fn():
x = 5
def inner_fn():
print(x)
inner_fn()
outer_fn() # Output: 5
For more information, please see Scopes.