You use inner functions to protect them from anything 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)

Recursive factorial

def factorial(number):

    # error handling
    if not isinstance(number, int):
        raise TypeError("Sorry. 'number' must be an integer.")
    if not number >= 0:
        raise ValueError("Sorry. 'number' must be zero or positive.")

    def inner_factorial(number):
        if number <= 1:
            return 1
        return number*inner_factorial(number-1)
    return inner_factorial(number)

# call the outer function
print(factorial(4))

A closure simply causes the inner function to remember the state of its environment when called

def generate_power(number):
    # define the inner function ...
    def nth_power(power):
        return number ** power
    # ... which is returned by the factory function

    return nth_power

raise_two = generate_power(2)
raise_three = generate_power(3)
print(raise_two(7))
print(raise_three(5))