Python Closures

Sharing is caring!

Last Updated on November 12, 2022 by Jay

Python closures are an intermediate programming concept, we should know about them and add this tool to our ever-expanding toolbelt. Said in its most succinct way, a closure is a function that returns another function with data bound to it. We can use closures to bridge the gap between functions and objects to give us function instances. When we need to associate some data with a function or have many different versions of what is essentially the same function.

Creating Closures in Python

Given the definition above, the first thing we need in order to create a closure is the ability to return a function from another function. Luckily, Python includes this ability in a simple and obvious way – you just have to nest the functions:

def closure(var):
    var += "?"
    
    def wrapped_func():
        print(var)
        
    return wrapped_func

closure_instance = closure("What")

# Call the function that's returned
closure_instance()

There are a couple of things to point out here:

  • Note how the scope of each function follows the standard whitespace indention rules. It’s exactly what we might expect a function within a function (a nested function) to look like.
  • Even though the wrapped_func is not explicitly passed the variable var it can still make reference to it and use it, just like a global variable could be referenced inside the smaller scope of a single function for example.
  • The function closure returns the entire function wrapped_func. This is obvious that we didn’t add parentheses to the end of the function in order to call it instead of merely referencing it. If that line of code was return wrapped_func() then it would have simply called the function once instead of returning the function to call again later whenever you want.
  • If we didn’t add the last line of code, it will not print anything. We can see that by commenting out the last line.
  • Also note that the following code will print the numbers in ascending order:
def closure(var):
    print(2)
    var += "?"
    
    def wrapped_func():
        print(5)
        print(var)
        print(6)
        
    print(3)
    return wrapped_func

print(1)
closure_instance = closure("What")

# Call the function that's returned
print(4)
closure_instance()
print(7)

Nonlocal keyword

What if we want to edit the variable while it’s inside the wrapped function? That’s when we need to include the nonlocal keyword:

  1. To indicate to the interpreter that this is not the first time it’s seen this variable (despite it being the first time it’s seen this variable in this scope)
  2. and to instead look in the scopes larger than the current one.
def closure(var):
    var += "?"
    
    def wrapped_func():
        nonlocal var
        var += "!"
        print(var)
        
    return wrapped_func

closure_instance = closure("What")

# Call the function that's returned
closure_instance()

The Function Factory

One of the main uses of closures is how they can create many different versions of the same basic function. This is the functional equivalent of having multiple instances of the same object. It would come in handy whenever we abstract our functions a little too far and need slight variations in a more base function before actually utilizing it in a reasonable way. A symptom of a too-abstract function might be that you have too many arguments in a function. Obviously, it’s up to the individuals to decide whether a function is too abstract or has too many arguments. However, it’s useful to know that closures are a way we can help find the balance. Consider the following code:

def filler(char, num):
    def inner(text):
        chars = char * num
        print(
            f"{chars}{text}{chars}"
        )
    return inner

# create 2 versions of the function
five_plus_filler = filler("+", 5)
three_dash_filler = filler("-", 3)

# finally call the versions I've created
five_plus_filler("Hello, World!")
three_dash_filler("I made a closure")
# prints
# +++++Hello, World!+++++
# ---I made a closure---

The most abstract version of this function is we want to be able to wrap some text with a number of characters. By using Python closures like this, we have a lot of flexibility to create reusable functions that specify exactly which character and exactly how many times to use it. Consider this alternative code where we write the function directly, without using a closure:

def filler2(char, num, text):
    chars = char * num
    print(
        f"{chars}{text}{chars}"
    )

filler2("+", 5, "Hello, World!")
filler2("-", 3, "I made a closure")

Now each time I call the function I need to pass the char and num arguments. Even if I’ve already used the same ones many times before. Imagine if you had dozens of arguments you needed to pass to a function and only a few of them changed often but the flexibility to change them was still required. We could create the different versions by hand and risk implementation differences between the versions resulting in logical errors. We could pass all the arguments each time and make our code difficult to read and reason about. Or we could create a single base/abstract closure and for each new version of the inner function needed, we create a new instance of the closure and load it with the specifics we need.

Together Forever

So we understand that we can use a closure to bind data to a function. Let’s examine another way that binding can benefit us over time, specifically through the use of the nonlocal keyword to alter the bound variables each time we call the function.

def make_counter():
    next_value = 0
    def return_next_value():
        nonlocal next_value
        val = next_value
        next_value += 1
        return val
    return return_next_value

my_first_counter = make_counter()
my_second_counter = make_counter()

print(my_first_counter())   # 0
print(my_second_counter())  # 0
print(my_first_counter())   # 1
print(my_second_counter())  # 1
print(my_first_counter())   # 2
print(my_second_counter())  # 2

As you can see, we’ve made 1 closure here and 2 unique instances of it. The above setup shows:

  • Each instance keeps a separate count of the number of times it is called.
  • The variable next_value stays within the scope of each function instance it can still increment when calling the outer scope function
  • Somewhat magically it keeps an internal record of the state of the variables, something usually reserved for objects in object-oriented programming

This is a simple example of how closures can blur the boundaries of scope in our code. Obviously, we can do much more than simply increment the value of our bound variables by 1 each time. This feature can come in handy for a number of tasks, such as avoiding global variables, for example.

Functions that Return Functions that Return Functions

So we’ve discussed how a function (that’s called a closure) can return another function. Let’s take it one step further and consider a closure that returns a closure. This would make it a function that returns a function that returns a function. This is a rabbit hole that goes as deep as you need it to go. Let’s take it step by step:

  1. A function allows you to input some parameters and return a value
  2. A closure allows you to input some parameters and return a unique function
  3. A closure that returns a closure allows you to input some parameters and return a unique closure

Why would we need to make a unique closure? Usually, this level of nesting functions is only useful in a few situations, such as:

  1. When you are nesting loops as well and want to perform unique calculations simply via a function.
  2. When we write code that other developers might use, like a library that allows you to create a specific type of closure more easily.
def outer_closure(a):
    def inner_closure(b):
        def func():
            return a + b
        return func
    return inner_closure 

oc = outer_closure(5)

ic1 = oc(4)
print(ic1())  # 4+5 = 9

ic2 = oc(2)
print(ic2())  # 2+5 = 7

Closures that return closures are specifically useful when we start talking about decorators in Python, but that is outside the scope of this article.

Can a closure return itself?

As a matter of fact, we can create a recursive closure in Python… or at least sort of. The problem comes when you realize this is:

  1. Severely limited in that the only thing it can return is itself, so in that way, it would only be useful for things that are less like a function and more like a subroutine or method
  2. Doesn’t actually bind the data you pass into the function in a meaningful or useful way, thereby not technically being a closure at all, and not doing much different than the equivalent straight-forward function would do.

With that said, it is possible for a function to return itself, so I will simply show you how and leave it up to your imagination to come up with a use case for this Python oddity:

def recursive_closure(var):
    print(var + "!")
    return recursive_closure 

print(1)
rc = recursive_closure("Hello")
print(2)
rc("Hi")
print(3)
rc("Hey")

Wrapping Up

In this article, we learned about:

  1. What Python closures are
  2. The syntax of how to write them in Python
  3. Considered some of the reasons why we might be interested in using them
  4. Discussed some rare and esoteric types of closures.

As it turns out, there are very few hard-and-fast rules in Python about what we can return from a function. Closures are one of the best results of this freedom.

Additional Resources

Object Oriented Programming In Python

About the Author

Eric Smith is a classically trained poet and self-taught programmer, studying Computer Information Systems at Boston University – Metropolitan. When he’s not working as a Data Scientist at a Fortune 500 company. Eric enjoys spending time with his wife, their two children, and four dogs.

You can find him online at:

Leave a Reply

Your email address will not be published. Required fields are marked *