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.
Returning Functions
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.
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.
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)
Python looks for variables in this order:
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) # looks in enclosing scope
inner_fn()
outer_fn() # Output: 5