Skip to main content

Nested Functions

Updated Oct 28, 2019 ·

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:

  1. Python enters outer_fn with x = 5 when calling:

    add_num = outer_fn(5)

    Notes:

    • The inner function inner_fn(y) is defined but not executed.
    • inner_fn(y) remembers x = 5 from the outer function (closure).
    • outer_fn returns inner_fn.
    • The variable add_num now points to this inner function.
    • Calling add_num(y) will execute inner_fn with y as input.
  2. When this line runs:

    print(add_num(3))

    Python passes 3 as y to the inner function stored in add_num. It calculates x + 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.

  1. Python enters raise_val with n = 2 when calling:

    square = raise_val(2)

    Notes:

    • The inner_fn(x) is defined but not executed.
    • The inner_fn(x) remembers n = 2 from raise_val (this is the closure).
    • The raise_val returns inner_fn
    • The variable square now points to this function.
    • Now, square(x) calls inner_fn with x as input.
  2. When this line is ran:

    print(square(4))

    Python passes 4 as x to the inner function stored in square. It calculates x ** 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()
info

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:

  1. Python enters counter() and sets n = 0.

  2. The inner function inc() is defined but not executed yet.

  3. counter() returns inc, so c now points to inc.

  4. When c() is called the first time:

    • Python runs inc() with access to n from the enclosing scope.
    • n is increased by 1 (n = 1) and returned.
    info

    When you use nonlocal n inside inc, Python looks up one level to the enclosing scope (the outer function counter) to find n.

  5. When c() is called the second time:

    • inc() runs again, remembering the previous n = 1.
    • n is 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.

info

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.