Movatterモバイル変換


[0]ホーム

URL:


— FREE Email Series —

🐍 Python Tricks 💌

Python Tricks Dictionary Merge

🔒 No spam. Unsubscribe any time.

Browse TopicsGuided Learning Paths
Basics Intermediate Advanced
apibest-practicescareercommunitydatabasesdata-sciencedata-structuresdata-vizdevopsdjangodockereditorsflaskfront-endgamedevguimachine-learningnumpyprojectspythontestingtoolsweb-devweb-scraping

Table of Contents

Python Closures: Common Use Cases and Examples

Python Closures: Common Use Cases and Examples

byLeodanis Pozo RamosPublication date Oct 30, 2024Reading time estimate 20mintermediatepython

Table of Contents

Remove ads

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 whatclosures are and how they work in Python
  • Get to know commonuse cases of closures
  • Explorealternatives to closures

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:


Python Closures: Common Use Cases and Examples

Interactive Quiz

Python Closures: Common Use Cases and Examples

In 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

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.

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:

Python
>>>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.

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 makeouter_func() return a closure object:

Python
>>>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:

  1. 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.

  2. 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.

  3. 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:

Python
>>>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.

Captured Variables

As you’ve learned, a closure retains the variables from its enclosing scope. Consider the following toy example:

Python
>>>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:

Python
>>>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:

Python
>>>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.

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:

Python
>>>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.

Building Stateful Functions

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:

Python
>>>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.

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:

Pythonapp.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.

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:

  1. Function-based decorators
  2. 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 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:

Python
>>>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:

Python
>>>@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.

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:

Python
>>>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:

Python
>>>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:

Python
>>>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:

Python
>>>@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.

Achieving Encapsulation With Closures

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:

Pythonstack_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:

Python
>>>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:

Pythonstack_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:

Python
>>>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:

Python
>>>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?

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 outPython’s.__call__() Method: Creating Callable Instances.

To illustrate, get back to themake_root_calculator() factory function:

Python
>>>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:

Pythonroots.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:

Python
>>>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.

Conclusion

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:

  • Whatclosures are and how they work in Python
  • When closurescan be used in practice
  • Howcallable 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:


Python Closures: Common Use Cases and Examples

Interactive Quiz

Python Closures: Common Use Cases and Examples

In 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.

Python Tricks Dictionary Merge

AboutLeodanis Pozo Ramos

Leodanis is a self-taught Python developer, educator, and technical writer with over 10 years of experience.

» More about Leodanis

Each tutorial at Real Python is created by a team of developers so that it meets our high quality standards. The team members who worked on this tutorial are:

MasterReal-World Python Skills With Unlimited Access to Real Python

Locked learning resources

Join us and get access to thousands of tutorials, hands-on video courses, and a community of expert Pythonistas:

Level Up Your Python Skills »

MasterReal-World Python Skills
With Unlimited Access to Real Python

Locked learning resources

Join us and get access to thousands of tutorials, hands-on video courses, and a community of expert Pythonistas:

Level Up Your Python Skills »

What Do You Think?

Rate this article:

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.


Looking for a real-time conversation? Visit theReal Python Community Chat or join the next“Office Hours” Live Q&A Session. Happy Pythoning!

Keep Learning

Related Topics:intermediatepython

Related Tutorials:

Keep reading Real Python by creating a free account or signing in:

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

Python Closures: Common Use Cases and Examples (Sample Code)

🔒 No spam. We take your privacy seriously.


[8]ページ先頭

©2009-2025 Movatter.jp