Table of Contents
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:
To get the most out of this tutorial, you should be familiar with several Python topics, includingfunctions,inner functions,decorators,classes, andcallable 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.
Aclosure is a function that retains access to itslexical 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 onfunctional programming, and Python supports closures as part of its wide variety of features.
In Python, a closure is a function that youdefine in andreturn from another function. This inner function can retain the objects defined in thenon-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.
In Python, aninner function is a function that you define inside another function. This type of function can access and updatenames in their enclosing function, which is the non-local scope.
Here’s a quick example:
>>>defouter_func():...name="Pythonista"...definner_func():...print(f"Hello,{name}!")...inner_func()...>>>outer_func()Hello, Pythonista!>>>greeter=outer_func()>>>print(greeter)None
In this example, you defineouter_func()
at the module level orglobal scope. Inside this function, you define thename
local variable. Then, you define another function calledinner_func()
. Because this second function lives in the body ofouter_func()
, it’s an inner or nested function. Finally, you call the inner function, which uses thename
variable defined in the enclosing function.
When you callouter_func()
,inner_func()
interpolatesname
into the greeting string and prints the result to your screen.
Note: To learn more about inner functions, check out thePython 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.
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 makeouter_func()
return a closure object:
>>>defouter_func():...name="Pythonista"...definner_func():...print(f"Hello,{name}!")...returninner_func...>>>outer_func()<function outer_func.<locals>.inner_func at 0x1066d16c0>>>>greeter=outer_func()>>>greeter()Hello, Pythonista!
In this new version ofouter_func()
, you return theinner_func
function object instead of calling it. When you callouter_func()
, you get a function object that’s a closure instead of a greeting message. This closure object remembers and can access the value ofname
even afterouter_func()
has returned. That’s why you get the greeting message when you callgreeter()
.
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 uselambda
functions to create closures:
>>>defouter_func():...name="Pythonista"...returnlambda:print(f"Hello,{name}!")...>>>greeter=outer_func()>>>greeter()Hello, Pythonista!
In this modified version ofouter_func()
, you use alambda
function to build the closure, which works like the original one.
As you’ve learned, a closure retains the variables from its enclosing scope. Consider the following toy example:
>>>defouter_func(outer_arg):...local_var="Outer local variable"...defclosure():...print(outer_arg)...print(local_var)...print(another_local_var)...another_local_var="Another outer local variable"...returnclosure...>>>closure=outer_func("Outer argument")>>>closure()Outer argumentOuter local variableAnother outer local variable
In this example,outer_arg
,local_var
, andanother_local_var
are all attached to the closure when you callouter_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 thenonlocal
statement. Consider the following example:
>>>defmake_counter():...count=0...defcounter():...nonlocalcount...count+=1...returncount...returncounter...>>>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 ofcount
, you use anonlocal
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 outPython’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:
>>>defmake_appender():...items=[]...defappender(new_item):...items.append(new_item)...returnitems...returnappender...>>>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, theitems
variable points to alist
object, which is mutable. In this case, you don’t have to use thenonlocal
keyword. You can modify the list in place.
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.
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:
>>>defmake_root_calculator(root_degree,precision=2):...defroot_calculator(number):...returnround(pow(number,1/root_degree),precision)...returnroot_calculator...>>>square_root=make_root_calculator(2,4)>>>square_root(42)6.4807>>>cubic_root=make_root_calculator(3)>>>cubic_root(42)3.48
Themake_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.
You can use closures to retainstate between function calls. These functions are known asstateful 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:
>>>defcumulative_average():...data=[]...defaverage(value):...data.append(value)...returnsum(data)/len(data)...returnaverage...>>>stream_average=cumulative_average()>>>stream_average(12)12.0>>>stream_average(13)12.5>>>stream_average(11)12.0>>>stream_average(10)11.5
Incumulative_average()
, thedata
local variable lets you retain the state between consecutive calls of the closure object that this function returns.
Next, you create a closure calledstream_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.
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
importtkinterastkapp=tk.Tk()app.title("GUI App")app.geometry("320x240")label=tk.Label(app,font=("Helvetica",16,"bold"),)label.pack()defcallback(text):defclosure():label.config(text=text)returnclosurebutton=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 theGreet button, the label displays the"Hello, World!"
message.
Note: To learn more about creating GUI apps with Tkinter, check out thePython GUI Programming With Tkinter tutorial.
Thecallback()
function returns a closure object that you can use to provide the button’scommand
argument. This argument acceptscallable objects that take no arguments. If you need to pass arguments as you did in the example, then you can use a closure.
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:
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 thePrimer 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:
>>>defdecorator(function):...defclosure():...print("Doing something before calling the function.")...function()...print("Doing something after calling the function.")...returnclosure...
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 thedecorator()
function has returned.
Here’s how you can use the decoratorsyntax to dynamically modify the behavior of a regular Python function:
>>>@decorator...defgreet():...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 yourgreet()
function. Note that now, when you callgreet()
, you get its original functionality plus the functionality added by the decorator.
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:
>>>defmemoize(function):...cache={}...defclosure(number):...ifnumbernotincache:...cache[number]=function(number)...returncache[number]...returnclosure...
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 thecache
dictionary along with the input function’s result.
Note: Python comes with amemoization built into the standard library. If you need caching in your projects, then you can use@cache
or@lru_cache
from thefunctools
module.
Now, say that you have the following toy function that mimics a costly computation:
>>>fromtimeimportsleep>>>defslow_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 thesleep()
function from thetime
module.
You can measure the function’s execution time using the following code:
>>>fromtimeitimporttimeit>>>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 thetimeit()
function from thetimeit
module to find out the execution time ofslow_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 decorateslow_operation()
using@memoize
as shown below. Then, run the timing code:
>>>@memoize...defslow_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 theslow_operation()
function isn’t running for repeated input values.
Inobject-oriented programming (OOP), classes provide a way to combine data and behavior in a single entity. A common requirement in OOP isdata 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 theObject-Oriented Programming (OOP) in Python tutorial.
In Python, achieving strong data encapsulation can be a difficult task because there’s no distinction betweenprivate andpublic attributes. Instead, Python uses a naming convention to communicate whether a given class member ispublic 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 followingStack
class:
stack_v1.py
classStack:def__init__(self):self._items=[]defpush(self,item):self._items.append(item)defpop(self):returnself._items.pop()
ThisStack
class stores its data in alist
object called._items
and implements common stack operations, such aspush andpop.
Here’s how you can use this class:
>>>fromstack_v1importStack>>>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 theHow 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
defStack():_items=[]defpush(item):_items.append(item)defpop():return_items.pop()defclosure():passclosure.push=pushclosure.pop=popreturnclosure
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.
Theclosure()
inner function is a placeholder for your closure. On top of this function, you add thepush()
andpop()
functions. Finally, you return the resulting closure.
You can use theStack()
function mostly in the same way you used theStack
class. One significant difference is that now you don’t have access to._items
:
>>>fromstack_v2importStack>>>stack=Stack()>>>stack.push(1)>>>stack.push(2)>>>stack.push(3)>>>stack.pop()3>>>stack._itemsTraceback (most recent call last):...AttributeError:'function' object has no attribute '_items'
TheStack()
function allows you to create closures that work as if they were instances of theStack
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 calledcell_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?
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 outPython’s.__call__()
Method: Creating Callable Instances.
To illustrate, get back to themake_root_calculator()
factory function:
>>>defmake_root_calculator(root_degree,precision=2):...defroot_calculator(number):...returnround(pow(number,1/root_degree),precision)...returnroot_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 theroot_degree
andprecision
arguments in its extended scope. You can replace this factory function with the following class:
roots.py
classRootCalculator:def__init__(self,root_degree,precision=2):self.root_degree=root_degreeself.precision=precisiondef__call__(self,number):returnround(pow(number,1/self.root_degree),self.precision)
This class takes the same two arguments asmake_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:
>>>fromrootsimportRootCalculator>>>square_root=RootCalculator(2,4)>>>square_root(42)6.4807>>>cubic_root=RootCalculator(3)>>>cubic_root(42)3.48>>>cubic_root.root_degree3
As you can conclude, theRootCalculator
works pretty much the same as themake_root_calculator()
function. As a plus, you now have access to configuration arguments likeroot_degree
.
Now you know that aclosure 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:
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.
🐍 Python Tricks 💌
Get a short & sweetPython Trick delivered to your inbox every couple of days. No spam ever. Unsubscribe any time. Curated by the Real Python team.
AboutLeodanis Pozo Ramos
Leodanis is a self-taught Python developer, educator, and technical writer with over 10 years of experience.
» More about LeodanisMasterReal-World Python Skills With Unlimited Access to Real Python
Join us and get access to thousands of tutorials, hands-on video courses, and a community of expert Pythonistas:
MasterReal-World Python Skills
With Unlimited Access to Real Python
Join us and get access to thousands of tutorials, hands-on video courses, and a community of expert Pythonistas:
What Do You Think?
What’s your #1 takeaway or favorite thing you learned? How are you going to put your newfound skills to use? Leave a comment below and let us know.
Commenting Tips: The most useful comments are those written with the goal of learning from or helping out other students.Get tips for asking good questions andget answers to common questions in our support portal.
Keep Learning
Already have an account?Sign-In
Almost there! Complete this form and click the button below to gain instant access:
Python Closures: Common Use Cases and Examples (Sample Code)