Functions II

Introduction to Closures

Learn how to return functions from other functions and use this technique to create closures. Discover what closures are and how they can be used to create function factories and a kind of 'protected memory'.


Let's begin with a simple example of a function nested inside another function.

def outer_function(message): def inner_function(): print(message) inner_function() outer_function('Hello')
Python
Output

outer_function takes the message paramter and defines a nested function called inner_function, which it immediately executes.

The inner_function prints the value of message, which it can access through the enclosing scope of outer_function.

I know it seems a bit overly complicated to use all this code just to print a simple message.

Things get interesting when the outer_function doesn't just execute the inner_function, but instead returns it:

def outer_function(message): def inner_function(): print(message) #### returning the nested function #### return inner_function
Python

Now we can assign the nested function to a new variable and execute it outside outer_function:

def outer_function(message): def inner_function(): print(message) #### returning the nested function #### return inner_function #### Storing the nested function in a new variable greet = outer_function('Hello') #### Calling the nested function #### greet()
Python
Output

In this way, you could say that the nested function has "escaped" the outer_function.

The key takeaway is that it still retains access to the original value of message from its enclosing scope even though outer_function has finished executing.

This is what's called a closure.

A closure is created when a nested function "remembers" the variables from its enclosing scope even after the outer function has finished executing.

This means the inner function can still access and use those variables.

In our example, we can now create different versions of the inner_function by passing different values to the outer_function:

def outer_function(message): def inner_function(): print(message) return inner_function #### create different versions of inner_function #### greet = outer_function('Hello') farewell = outer_function('Goodbye') greet() farewell()
Python
Output

Now, we've used the outer_function as a function factory.

A function factory is a function that produces other functions, allowing you to create specialized behavior based on input parameters.


What will be the output?

def outer(x): y = 5 def inner(): print(x * y) return inner func = outer(100) func()
Python

What will be the output?

def outer(x): y = 5 def inner(): x = 10 print(x * y) return inner func = outer(100) func()
Python

Function factories are just one of many use cases for closures.

You can also use closures to manage a state that you want to keep hidden from the outside world.

For example, here we create a create_counter function that manages the state of a count variable:

def create_counter(): count = 0 # Protected state def increment(): nonlocal count # Access the enclosing function's variable count += 1 print(count) return increment my_counter = create_counter() my_counter() my_counter() my_counter()
Python
Output

The nonlocal keyword allows the inner function to modify the count variable from the enclosing create_counter function.

The moment we execute the create_counter function, we create a new instance of the count variable.

This value is not directly accessible to us. It can only be modified by the nested function increment.

However, this nested function is available to use because the outer function returned it, and we assigned it to the my_counter variable.

Be executing my_counter, we can indirectly modify the count.


What will be the output?

def create_counter(): count = 0 def increment(): nonlocal count count += 1 print(count) return increment func = create_counter() print(count)
Python

What will be the output?

def create_counter(): count = 0 def increment(): nonlocal count count += 2 print(count) return increment func = create_counter() func() func() func()
Python

What will be the output?

def create_counter(): count = 0 def increment(): nonlocal count count += 1 print(count) return increment func1 = create_counter() func2 = create_counter() func1() func2() func1() func2()
Python

What will be the output?

def create_counter(step): count = 0 def increment(): nonlocal count count += step print(count) return increment func = create_counter(5) func() func()
Python

What will be the output?

def create_counter(step): count = 0 def increment(): nonlocal count count += step print(count) return increment func1 = create_counter(5) func2 = create_counter(2) func1() func1() func2() func2()
Python

What will be the output?

def create_counter(): count = 0 def increment(step): nonlocal count count += step print(count) return increment func = create_counter() func(1) func(2) func(3)
Python