Day 35: Nested Functions in Python

In layman term, it is an inner function defined inside a function. There is few reasons why sometimes we need to use nested functions.
Encapsulation
You use inner functions to protectย them from everything happening outside of the function, meaning that they are hidden from the global scope.

def outer(num1):
    def inner_increment(num1):  # Hidden from outer code
        return num1 + 1
    num2 = inner_increment(num1)
    print(num1, num2)

inner_increment(10)
# outer(10)

When we try to execute the above codes, it throws error,
name ‘inner_increment’ is not defined.

Now, try again by commenting the line of code, inner_increment(10) and uncomment the line of code, outer(10), then execute the codes. It returns us a result with two values because the print statement has 2 values.

We cannot access to the inner function (nested function) when we tried to call inner_increment() because it is hidden from the global scope. By calling the outer function, outer() and pass in an argument, it

Another example,

When we try to execute this code by calling the raise_val(), we do not need to repeatedly write the codes twice.

#function call
square = raise_val(2)
cube = raise_val(3)
print(square(2), cube(4))

#output
#4 64

I have a question before proceed, how does the line of code works?
print(square(2), cube(4))

While n value (argument) for function raise_val() is 2 and 3 respectively. the variable square and cube pass an argument too.

Keeping it DRY
Maybe, you have a function that performs the same chunk of code in numerous places. DRY means “don’t repeat yourself”. In an example I found online, you might write a function that processes a file, and you want to accept either an open file object or a file name.

The code looks like,

def process(file_name):
    def do_stuff(file_process):
        for line in file_process:
            print(line)
    if isinstance(file_name, str):
        with open(file_name, 'r') as f:
            do_stuff(f)
    else:
        do_stuff(file_name)
# Define three_shouts
def three_shouts(word1, word2, word3):
    """Returns a tuple of strings
    concatenated with '!!!'."""

    # Define inner
    def inner(word):
        """Returns a string concatenated with '!!!'."""
        return word + '!!!'

    # Return a tuple of strings
    return (inner(word1), inner(word2), inner(word3))

# Call three_shouts() and print
print(three_shouts('a', 'b', 'c'))

#Output returns a tuple of 3 elements
#('a!!!', 'b!!!', 'c!!!') 

Remember that assigning names will only create or change local names, unless they are declared in global or nonlocal statements using keywords, global or nonlocal.

The syntax,

def outer():
  n = 1
  def inner():
    nonlocal n
    n = 2
    print(n)
  inner()
  print(n)

The above code alters the value of n in the enclosing scope. When outer() function is called, the n = 1 has changes its value by inner() function using the keyword, nonlocal n. Therefore, both print statements return 2.

Source:
https://realpython.com/inner-functions-what-are-they-good-for/