The provided content explains the concept of closures in Python, detailing their definition, characteristics, and practical usage, while also addressing common misunderstandings and demonstrating how to implement closures effectively.
Abstract
Python's closure technique is a powerful feature that allows functions to remember and access variables from their enclosing scope even after the outer function has finished executing. Closures are a subset of nested functions, requiring the inner function to use and retain variables from the outer function, which must return the inner function for closure to occur. The content delves into the intricacies of closures, including how to access enclosed values using the __closure__ attribute, common pitfalls such as referencing mutable variables, and strategies to avoid bugs. It also highlights the role of closures in creating decorators and in encapsulating private variables more securely than Python's conventional underscore method. The article emphasizes the importance of understanding closures for writing functional style programs and provides examples and tips for mastering this advanced Python feature.
Opinions
Closures in Python are a sophisticated technique that can lead to more elegant and functional programming styles when used correctly.
Distinguishing closures from nested functions is crucial, as not all nested functions are closures.
The article suggests that while closures are a valuable tool, they require careful implementation to avoid common issues such as all functions capturing the same value.
Understanding closures is non-trivial and requires a deep comprehension of Python's scoping rules, particularly the LEGB rule.
The author posits that closures can be used to effectively hide private variables, offering a level of encapsulation not natively available in Python.
The use of closures in Python is likened to an "advanced weapon," implying that it is powerful but should be wielded with caution, especially by beginners.
The article encourages the use of lambda functions to simplify closure code, suggesting a preference for concise and readable code.
Closures are presented as an extension of functional programming in Python, with decorators being cited as a practical application of closures.
5 Levels of Understanding Closures in Python
Introduction
Functions in Python are first-class citizens. This means we can operate functions like other objects:
Pass functions as arguments
Assign a function to a variable
Return a function from another function (nested functions)
Based on these, Python supports a powerful technique: closure.
Most of us heard about the closure, but totally understand it and use it well are not easy.
Fortunately, this post will uncover the mystery of closures layer by layer. It’s time to make a cup of coffee and enjoy this technique. ☕️
Level 0: Understand What the Closure Is
The closure is a concept in the context of nested functions. Let’s see it by an interesting example:
The above example could be very wired if you haven’t known closures yet. We have already deleted the outer_func function. However, the f() can still print “Yang Zhou”, which is the local variable of the outer_func function. Why its local variable is still alive after removing the function?
Obviously, it is not because Yang Zhou is a magician. This is the effect of the closure technique.
Operationally, a closure is a record storing a function together with an environment. (Wikipedia)
As to our example, the inner function print_leader can “remember” the variables in the outer function.
In a word, the closure in Python is a function which remembers values in its enclosing scope.
Level 1: Distinguish Closures From Nested Functions
Although closures are in the context of nested functions, nested functions are not necessarily closures. The appearance of the closure must meet three conditions:
There are nested functions.
The inner function must use variables defined in its outer function.
The outer function must return the inner function.
Let’s change a little bit of the previous example:
As the above shown, if we return the result of the inner function. No closures will occur. The f is a NoneType since it didn’t receive the print_leader function, the print_leader function has already executed.
Level 2: Know How to Get the Enclosed Values
In fact, every function in Python has a special attribute called __closure__, which stores all the “remembered” values.
As the above example shown, the outer_func is not a closure and its __closure__ attribute is None. In the other hand, the __closure__ of f contains a cell object which saves the “remembered” value.
Level 3: Implement a Bug-Free Closure
Closures are not easy to implement and we should use them carefully.
A negative example
Let’s look at an example program, which is going to save three functions in a list and run them one by one to print three different even numbers:
The results of the above example are not as expected. Why all the three functions printed the same values?
Review the code again and we can see that the enclosed variable i was already equal to 2 when we really run the f1(), f2(), and f3() in the print() method. If we print their __closure__ attributes, the results are as following:
An interview question
Based on the above example, here comes a good interview question:
How to change the code to print three different values by different functions?
As the above shown, we use a variable j to receive parameter i, and the results are as expected! However, this time the three functions are not closures since they don’t need to “remember” any variables in their enclosing scope.
Recall the second condition of the 3 conditions to build a closure mentioned in level 1:
The inner function must use variables defined in its outer function.
In the above example, the variable i was not used in the inner function. It just passed a value as an argument to the parameter j of the inner function. Therefore, no closure was created.
A lesson learned from the above examples
We should be careful when the inner function references some enclosing variables which will be changed later.
Level 4: Use Closures Skilfully
The closure is an advanced weapon of Python. It could be a little hard for beginners. However, if we can totally understand it and use it skilfully, it will help us a lot. Actually, decorators in Python are just extensive applications of closures.
In this final level, two significant tips will be shown to help you master closures.
Use lambda function to simplify the code
We can make the previous example code more elegant by the lambda function:
Closures hide private variables more effectively
Python doesn’t have built-in keywords like public or private to control the accessibility of variables. By convention, we use double underscores to define a private member of a class. But the “private” variable can still be accessed.
Sometimes, we need stronger protections to a hidden variable. Closures can help!
As the example6.py shown, it’s more difficult to get and change the value of the leader variable in the function f. The leader variable is more “private”.
Conclusion
Python supports the closure, which is a functional programming relative technique. After completely understanding it, we can use it to write more elegant and functional style programs.