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 + y5 + 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 ** n4 ** 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.