In Python, a closure is typically a function defined inside another function. This inner function grabs the objects defined in its enclosing scope and associates them with the inner function object itself. The resulting combination is called a closure.
Closures are a common feature in functional programming languages. In Python, closures can be pretty useful because they allow you to create function-based decorators, which are powerful tools.
In this tutorial, you’ll:
- Learn what closures are and how they work in Python
- Get to know common use cases of closures
- Explore alternatives to closures
To get the most out of this tutorial, you should be familiar with several Python topics, including functions, inner functions, decorators, classes, and callable instances.
Get Your Code: Click here to download the free sample code that shows you how to use closures in Python.
Take the Quiz: Test your knowledge with our interactive “Python Closures: Common Use Cases and Examples” quiz. You’ll receive a score upon completion to help you track your learning progress:
Interactive Quiz
Python Closures: Common Use Cases and ExamplesIn this quiz, you'll test your understanding of Python closures. Closures are a common feature in functional programming languages and are particularly popular in Python because they allow you to create function-based decorators.
Getting to Know Closures in Python
A closure is a function that retains access to its lexical scope, even when the function is executed outside that scope. When the enclosing function returns the inner function, then you get a function object with an extended scope.
In other words, closures are functions that capture the objects defined in their enclosing scope, allowing you to use them in their body. This feature allows you to use closures when you need to retain state information between consecutive calls.
Closures are common in programming languages that are focused on functional programming, and Python supports closures as part of its wide variety of features.
In Python, a closure is a function that you define in and return from another function. This inner function can retain the objects defined in the non-local scope right before the inner function’s definition.
To better understand closures in Python, you’ll first look at inner functions because closures are also inner functions.
Inner Functions
In Python, an inner function is a function that you define inside another function. This type of function can access and update names in their enclosing function, which is the non-local scope.
Here’s a quick example:
>>> def outer_func():
... name = "Pythonista"
... def inner_func():
... print(f"Hello, {name}!")
... inner_func()
...
>>> outer_func()
Hello, Pythonista!
>>> greeter = outer_func()
>>> print(greeter)
None
In this example, you define outer_func()
at the module level or global scope. Inside this function, you define the name
local variable. Then, you define another function called inner_func()
. Because this second function lives in the body of outer_func()
, it’s an inner or nested function. Finally, you call the inner function, which uses the name
variable defined in the enclosing function.
When you call outer_func()
, inner_func()
interpolates name
into the greeting string and prints the result to your screen.
Note: To learn more about inner functions, check out the Python Inner Functions: What Are They Good For? tutorial.
In the above example, you defined an inner function that can use the names in the enclosing scope. However, when you call the outer function, you don’t get a reference to the inner function. The inner function and the local names won’t be available outside the outer function.
In the following section, you’ll learn how to turn an inner function into a closure, which makes the inner function and the retained variables available to you.
Function Closures
All closures are inner functions, but not all inner functions are closures. To turn an inner function into a closure, you must return the inner function object from the outer function. This may sound like a tongue twister, but here’s how you can make outer_func()
return a closure object:
>>> def outer_func():
... name = "Pythonista"
... def inner_func():
... print(f"Hello, {name}!")
... return inner_func
...
>>> outer_func()
<function outer_func.<locals>.inner_func at 0x1066d16c0>
>>> greeter = outer_func()
>>> greeter()
Hello, Pythonista!
In this new version of outer_func()
, you return the inner_func
function object instead of calling it. When you call outer_func()
, you get a function object that’s a closure instead of a greeting message. This closure object remembers and can access the value of name
even after outer_func()
has returned. That’s why you get the greeting message when you call greeter()
.
To create a Python closure, you need the following components:
-
An outer or enclosing function: This is a function that contains another function, often referred to as the inner function. The outer function can take arguments and define variables that the inner function can access and update.
-
Variables that are local to the outer function: These are variables from its enclosing scope. Python retains these variables, allowing you to use them in the closure, even after the outer function has returned.
-
An inner or nested function: This is a function defined inside the outer function. It can access and update the variables from the outer function even after the outer function has returned.
In this section’s example, you have an outer function, a local variable (name
), and an inner function. The final step to getting a closure object from this combination is to return the inner function object from the outer function.
It’s important to note that you can also use lambda
functions to create closures:
>>> def outer_func():
... name = "Pythonista"
... return lambda: print(f"Hello, {name}!")
...
>>> greeter = outer_func()
>>> greeter()
Hello, Pythonista!
In this modified version of outer_func()
, you use a lambda
function to build the closure, which works like the original one.
Captured Variables
As you’ve learned, a closure retains the variables from its enclosing scope. Consider the following toy example:
>>> def outer_func(outer_arg):
... local_var = "Outer local variable"
... def closure():
... print(outer_arg)
... print(local_var)
... print(another_local_var)
... another_local_var = "Another outer local variable"
... return closure
...
>>> closure = outer_func("Outer argument")
>>> closure()
Outer argument
Outer local variable
Another outer local variable
In this example, outer_arg
, local_var
, and another_local_var
are all attached to the closure when you call outer_func()
, even if its containing scope is no longer available. However, closure()
can access these variables because they’re now part of the closure itself. That’s why you can say that a closure is a function with an extended scope.
Closures can also update the value of these variables, and this can result in two scenarios: the variables can point to either an immutable or mutable object.
To update the value of a variable that points to an immutable object, you need to use the nonlocal
statement. Consider the following example:
>>> def make_counter():
... count = 0
... def counter():
... nonlocal count
... count += 1
... return count
... return counter
...
>>> counter = make_counter()
>>> counter()
1
>>> counter()
2
>>> counter()
3
In this example, count
holds a reference to an integer value, which is immutable. To update the value of count
, you use a nonlocal
statement that tells Python you want to reuse the variable from the non-local scope.
Note: To dive deeper into mutable an immutable objects, check out Python’s Mutable vs Immutable Types: What’s the Difference?
When your variable points to a mutable object, you can modify the variable’s value in place:
>>> def make_appender():
... items = []
... def appender(new_item):
... items.append(new_item)
... return items
... return appender
...
>>> appender = make_appender()
>>> appender("First item")
['First item']
>>> appender("Second item")
['First item', 'Second item']
>>> appender("Third item")
['First item', 'Second item', 'Third item']
In this example, the items
variable points to a list
object, which is mutable. In this case, you don’t have to use the nonlocal
keyword. You can modify the list in place.
Creating Closures to Retain State
In practice, you can use a Python closure in several different situations. In this section, you’ll explore how to use closures to create factory functions, maintain state across function calls, and implement callbacks, enabling more dynamic, flexible, and efficient code.
Creating Factory Functions
You can write functions to build closures with some initial configuration or parameters. This is particularly handy when you need to create multiple similar functions with different settings.
For example, say that you want to compute numeric roots with different degrees and result precisions. In this situation, you can code a factory function that returns closures with predefined degrees and precisions like the example below:
>>> def make_root_calculator(root_degree, precision=2):
... def root_calculator(number):
... return round(pow(number, 1 / root_degree), precision)
... return root_calculator
...
>>> square_root = make_root_calculator(2, 4)
>>> square_root(42)
6.4807
>>> cubic_root = make_root_calculator(3)
>>> cubic_root(42)
3.48
The make_root_calculator()
is a factory function that you can use to create functions that compute different numeric roots. In this function, you take the root degree and the desired precision as configuration parameters.
Then, you define an inner function that takes a number as an argument and computes the specified root with the desired precision. Finally, you return the inner function, creating a closure.
You can use this function to create closures that allow you to compute numeric roots of different degrees, like square and cubic roots. Note that you can also tweak the result’s precision.
Building Stateful Functions
You can use closures to retain state between function calls. These functions are known as stateful functions, and closures are a way to build them.
For example, say that you want to write a function that takes consecutive numeric values from a data stream and computes their cumulative average. Between calls, the function must keep track of previously passed values. In this situation, you can use the following function:
>>> def cumulative_average():
... data = []
... def average(value):
... data.append(value)
... return sum(data) / len(data)
... return average
...
>>> stream_average = cumulative_average()
>>> stream_average(12)
12.0
>>> stream_average(13)
12.5
>>> stream_average(11)
12.0
>>> stream_average(10)
11.5
In cumulative_average()
, the data
local variable lets you retain the state between consecutive calls of the closure object that this function returns.
Next, you create a closure called stream_average()
and call it with different numeric values. Note how this closure remembers the previously passed values and computes the average by adding the newly provided value.
Providing Callback Functions
Closures are commonly used in event-driven programming when you need to create callback functions that carry additional context or state information. Graphical user interface (GUI) programming is a good example of where these callback functions are used.
To illustrate, suppose you want to create a "Hello, World!"
app with Tkinter, Python’s default GUI programming library. The app needs a label to show the greeting and a button to trigger it. Here’s the code for that little app:
app.py
import tkinter as tk
app = tk.Tk()
app.title("GUI App")
app.geometry("320x240")
label = tk.Label(
app,
font=("Helvetica", 16, "bold"),
)
label.pack()
def callback(text):
def closure():
label.config(text=text)
return closure
button = tk.Button(
app,
text="Greet",
command=callback("Hello, World!"),
)
button.pack()
app.mainloop()
This code defines a toy Tkinter app that consists of a window with a label and a button. When you click the Greet button, the label displays the "Hello, World!"
message.
Note: To learn more about creating GUI apps with Tkinter, check out the Python GUI Programming With Tkinter tutorial.
The callback()
function returns a closure object that you can use to provide the button’s command
argument. This argument accepts callable objects that take no arguments. If you need to pass arguments as you did in the example, then you can use a closure.
Writing Decorators With Closures
Decorators are a powerful feature in Python. You can use decorators to modify a function’s behavior dynamically. In Python, you have two types of decorators:
- Function-based decorators
- Class-based decorators
A function-based decorator is a function that takes a function object as an argument and returns another function object with extended functionality. This latter function object is also a closure. So, to create function-based decorators, you use closures.
Note: To learn more about decorators, check out the Primer on Python Decorators tutorial.
As you already learned, decorators allow you to modify the behavior of functions without altering their internal code. In practice, function-based decorators are closures. The distinguishing characteristic is that their main goal is to modify the behavior of the function that you pass as an argument to the closure-containing function.
Here’s an example of a minimal decorator that adds messages on top of the input function’s functionality:
>>> def decorator(function):
... def closure():
... print("Doing something before calling the function.")
... function()
... print("Doing something after calling the function.")
... return closure
...
In this example, the outer function is the decorator. This function returns a closure object that modifies the original behavior of the input function object by adding extra features. The closure can act on the input function even after the decorator()
function has returned.
Here’s how you can use the decorator syntax to dynamically modify the behavior of a regular Python function:
>>> @decorator
... def greet():
... print("Hi, Pythonista!")
...
>>> greet()
Doing something before calling the function.
Hi, Pythonista!
Doing something after calling the function.
In this example, you use @decorator
to modify the behavior of your greet()
function. Note that now, when you call greet()
, you get its original functionality plus the functionality added by the decorator.
Implementing Memoization With Closures
Caching can improve an algorithm’s performance by avoiding unnecessary recomputation. Memoization is a common caching technique that prevents a function from running more than once for the same input.
Memoization works by storing the result for a given set of input arguments in memory and then referencing it later when necessary. You can use closures to implement memoization.
In the following toy example, you take advantage of a decorator, which is also a closure, to cache values that result from a costly hypothetical computation:
>>> def memoize(function):
... cache = {}
... def closure(number):
... if number not in cache:
... cache[number] = function(number)
... return cache[number]
... return closure
...
Here, memoize()
takes a function object as an argument and returns another closure object. The inner function runs the input function only for unprocessed numbers. The processed numbers are cached in the cache
dictionary along with the input function’s result.
Note: Python comes with a memoization built into the standard library. If you need caching in your projects, then you can use @cache
or @lru_cache
from the functools
module.
Now, say that you have the following toy function that mimics a costly computation:
>>> from time import sleep
>>> def slow_operation(number):
... sleep(0.5)
...
This function holds the code’s execution for just half a second to mimic a costly operation. To do this, you use the sleep()
function from the time
module.
You can measure the function’s execution time using the following code:
>>> from timeit import timeit
>>> timeit(
... "[slow_operation(number) for number in [2, 3, 4, 2, 3, 4]]",
... globals=globals(),
... number=1,
... )
3.02610950000053
In this code snippet, you use the timeit()
function from the timeit
module to find out the execution time of slow_operation()
when you run this function with a list of values. To process the six input values, the code takes a bit more than three seconds. You can use memoization to make this computation more efficient by skipping the repeated input values.
Go ahead and decorate slow_operation()
using @memoize
as shown below. Then, run the timing code:
>>> @memoize
... def slow_operation(number):
... sleep(0.5)
...
>>> timeit(
... "[slow_operation(number) for number in [2, 3, 4, 2, 3, 4]]",
... globals=globals(),
... number=1,
... )
1.5151869590008573
Now the same code takes half the time because of the memoization technique. That’s because the slow_operation()
function isn’t running for repeated input values.
Achieving Encapsulation With Closures
In object-oriented programming (OOP), classes provide a way to combine data and behavior in a single entity. A common requirement in OOP is data encapsulation, a principle that recommends protecting an object’s data from the outside world and preventing direct access.
Note: To learn more about OOP in Python, check out the Object-Oriented Programming (OOP) in Python tutorial.
In Python, achieving strong data encapsulation can be a difficult task because there’s no distinction between private and public attributes. Instead, Python uses a naming convention to communicate whether a given class member is public or non-public.
You can use Python closures to achieve stricter data encapsulation. Closures allow you to create a private scope for data, preventing users from accessing that data. This helps maintain data integrity and prevent unintended modifications.
To illustrate, say that you have the following Stack
class:
stack_v1.py
class Stack:
def __init__(self):
self._items = []
def push(self, item):
self._items.append(item)
def pop(self):
return self._items.pop()
This Stack
class stores its data in a list
object called ._items
and implements common stack operations, such as push and pop.
Here’s how you can use this class:
>>> from stack_v1 import Stack
>>> stack = Stack()
>>> stack.push(1)
>>> stack.push(2)
>>> stack.push(3)
>>> stack.pop()
3
>>> stack._items
[1, 2]
Your class’s basic functionality works. However, even though the ._items
attribute is non-public, you can access its values using dot notation as you’d do with a normal attribute. This behavior makes it difficult to encapsulate data to protect it from direct access.
Note: To learn more about the stack data structure, take a look at the How to Implement a Python Stack tutorial.
Again, a closure provides a trick for achieving a stricter data encapsulation. Consider the following code:
stack_v2.py
def Stack():
_items = []
def push(item):
_items.append(item)
def pop():
return _items.pop()
def closure():
pass
closure.push = push
closure.pop = pop
return closure
In this example, you write a function to create a closure object instead of defining a class. Inside the function, you define a local variable called _items
, which will be part of your closure object. You’ll use this variable to store the stack’s data. Then, you define two inner functions that implement the stack operations.
The closure()
inner function is a placeholder for your closure. On top of this function, you add the push()
and pop()
functions. Finally, you return the resulting closure.
You can use the Stack()
function mostly in the same way you used the Stack
class. One significant difference is that now you don’t have access to ._items
:
>>> from stack_v2 import Stack
>>> stack = Stack()
>>> stack.push(1)
>>> stack.push(2)
>>> stack.push(3)
>>> stack.pop()
3
>>> stack._items
Traceback (most recent call last):
...
AttributeError: 'function' object has no attribute '_items'
The Stack()
function allows you to create closures that work as if they were instances of the Stack
class. However, you don’t have direct access to ._items
, improving your data encapsulation.
If you get really picky, you can use an advanced trick to access the content of ._items
:
>>> stack.push.__closure__[0].cell_contents
[1, 2]
The .__closure__
attribute returns a tuple of cells that contain bindings for the closure’s variables. A cell object has an attribute called cell_contents
, which you can use to get the value of the cell.
Even though this trick allows you to access grabbed variables, it’s not typically used in Python code. In the end, if you’re trying to achieve encapsulation, why would you break it?
Exploring Alternatives to Closures
So far, you’ve learned that Python closures can help solve several problems. However, it can be challenging to grasp how they work under the hood, so using an alternative tool can make your code easier to reason about.
You can replace a closure with a class that produces callable instances by implementing the .__call__()
special method. Callable instances are objects that you can call as you’d call a function.
Note: To learn more about callable instances, check out Python’s .__call__()
Method: Creating Callable Instances.
To illustrate, get back to the make_root_calculator()
factory function:
>>> def make_root_calculator(root_degree, precision=2):
... def root_calculator(number):
... return round(pow(number, 1 / root_degree), precision)
... return root_calculator
...
>>> square_root = make_root_calculator(2, 4)
>>> square_root(42)
6.4807
>>> cubic_root = make_root_calculator(3)
>>> cubic_root(42)
3.48
The function returns closures that retain the root_degree
and precision
arguments in its extended scope. You can replace this factory function with the following class:
roots.py
class RootCalculator:
def __init__(self, root_degree, precision=2):
self.root_degree = root_degree
self.precision = precision
def __call__(self, number):
return round(pow(number, 1 / self.root_degree), self.precision)
This class takes the same two arguments as make_root_calculator()
and turns them into instance attributes.
By providing the .__call__()
method, you transform your class instances into callable objects that you can call as regular functions. Here’s how you can use this class to build root calculator function-like objects:
>>> from roots import RootCalculator
>>> square_root = RootCalculator(2, 4)
>>> square_root(42)
6.4807
>>> cubic_root = RootCalculator(3)
>>> cubic_root(42)
3.48
>>> cubic_root.root_degree
3
As you can conclude, the RootCalculator
works pretty much the same as the make_root_calculator()
function. As a plus, you now have access to configuration arguments like root_degree
.
Conclusion
Now you know that a closure is a function object typically defined inside another function in Python. Closures grab the objects defined in their enclosing scope and combine them with the inner function object to create a callable object with an extended scope.
You can use closures in multiple scenarios, especially when you need to retain the state between consecutive function calls or write a decorator. So, knowing how to use closures can be an excellent skill for a Python developer.
In this tutorial, you’ve learned:
- What closures are and how they work in Python
- When closures can be used in practice
- How callable instances can replace closures
With this knowledge, you can start creating and using Python closures in your code, especially if you’re interested in functional programming tools.
Get Your Code: Click here to download the free sample code that shows you how to use closures in Python.
Take the Quiz: Test your knowledge with our interactive “Python Closures: Common Use Cases and Examples” quiz. You’ll receive a score upon completion to help you track your learning progress:
Interactive Quiz
Python Closures: Common Use Cases and ExamplesIn this quiz, you'll test your understanding of Python closures. Closures are a common feature in functional programming languages and are particularly popular in Python because they allow you to create function-based decorators.