This article was first published in ： Walker AI
python What is a closure in ？ What's the use of closures ？ Why use closures ？ Today we'll take this 3 One problem is to know closures step by step .
Closures and functions are closely related , It's necessary to introduce some background knowledge before introducing closures , Such as nested functions 、 The scope of variables and so on .
Scope is the range of variables that can be accessed at runtime , Variables defined in a function are local variables , The scope of action of local variables can only be within the internal scope of the function , It cannot be referenced outside a function .
The variables defined in the outermost layer of the module are global variables , It's globally visible , Of course, you can also read global variables in functions . You can't access local variables outside the function . for example ：
a = 1 def foo(): print(a) # 1 Copy code
def foo(): print(a) # NameError: name 'num' is not defined Copy code
2. Nested function
Functions can be defined not only in the outermost layer of a module , It can also be defined inside another function , Functions defined in functions like this are called nested functions
（nested function）. For nested functions , It has access to nonlocals declared in its outer scope
（non-local） Variable , For example, variables in the code example
a Can be nested functions
printer Normal visit .
def foo(): #foo It's a peripheral function a = 1 # printer It's a nested function def printer(): print(a) printer() foo() # 1 Copy code
So is there a possibility that even if it is out of the scope of the function itself , Local variables can also be accessed ？
The answer is closures ！
We change the above functions into higher order functions （ Accept a function as an argument , Or the function returned as a result is a higher-order function ） Writing .
def foo(): #foo It's a peripheral function a = 1 # printer It's a nested function def printer(): print(a) return printer x = foo() x() # 1 Copy code
This code has exactly the same effect as the previous example , The same output
1. The difference is in the internal function
printer Directly returned as a return value .
In general , Local variables in a function are only available during the execution of the function , once
foo() After execution , We would think of variables as
a Will no longer be available . However , Here we find
foo After execution , Calling
a The value of the variable is output normally , That's what closures do , Closures make it possible for local variables to be accessed outside the function .
People sometimes confuse closures with anonymous functions . There are historical reasons for that : Define the function inside the function Less common , Not until you start using anonymous functions . and , Closure problems occur only when nested functions are involved . therefore , Many people know these two concepts at the same time .
Actually , A closure is a function that extends its scope , It contains references in the function definition body 、 But not in the definition body Non global variables . It doesn't matter if the function is anonymous , The key is that it can access non global variables defined outside the definition body .
Generally speaking, closures , seeing the name of a thing one thinks of its function , It's a sealed package , It's wrapped in free variables , Just like property values defined in a class , The visible range of the free variable is accompanied by the package , Where can I access this package , Where can I access this free variable . Where is the package bound ？ Add a print sentence to the above code ：
def foo(): # foo It's a peripheral function a = 1 # printer It's a nested function def printer(): print(a) return printer x = foo() print(x.__closure__.cell_contents) # 1 Copy code
You can see that it's in the function object
__closure__ Properties of the ,
__closure__ Is a meta ancestor object function responsible for closure binding , Binding of free variables . The value of this property is usually
None, If this function is a closure , So what it returns is a result of
cell Tuple objects made up of objects .
cell_contents Attributes are free variables in closures . This explains why local variables are separated from functions , It can also be accessed outside the function , Because it's stored in the
cell_contents It's in .
4. The benefits of closures
Closures avoid using global variables , Besides , Closures allow functions to be manipulated with certain data （ Environmental Science ） Connected . This is very similar to object-oriented programming , In face object programming , Object allows us to put some data （ Object properties ） Associated with one or more methods .
Generally speaking , When there is only one method in an object , Using closures is a better choice . Let's take an example of calculating the mean value , If there's one called
avg Function of , Its function is to calculate the mean value of the increasing series of values ; for example , Throughout history The average closing price of a commodity . New prices are added every day , So the average takes into account all the prices so far , As shown below ：
10) #10.0 avg(11) #10.5 avg(12) #11.0 Copy codeavg(
In the past , We can design a class ：
class Averager(): def __init__(self): self.series =  def __call__(self, new_value): self.series.append(new_value) total = sum(self.series) return total/len(self.series) avg = Averager() avg(10) #10.0 avg(11) #10.5 avg(12) #11.0 Copy code
In this case, we use closures to implement .
def make_averager(): series =  def averager(new_value): series.append(new_value) total = sum(series) return total/len(series) return averager avg = make_averager() avg(10) #10.0 avg(11) #10.5 avg(12) #11.0 Copy code
make_averager when , Return to one
averager Function object . Every time you call
averager when , It adds parameters to the list , Then calculate the current average . This is more elegant than using classes to implement , In addition, decorators are also an application scenario based on closures .
5. The pit of closure
After reading the above explanation of closures, you think closures are nothing more than that ？ In actual use, we often fall into the trap inadvertently , Take a look at the following example ：
def create_multipliers(): return [lambda x: x * i for i in range(5)] for multiplier in create_multipliers(): print(multiplier(2)) # Expected output 0, 2, 4, 6, 8 # The result is 8, 8, 8, 8, 8 Copy code
We expect output
0, 2, 4, 6, 8. The result is
8, 8, 8, 8, 8. Why is this problem ？ Let's change the code ：
def create_multipliers(): multipliers = [lambda x: x * i for i in range(5)] print([m.__closure__.cell_contents for m in multipliers]) create_multipliers() # [4, 4, 4, 4, 4] Copy code
You can see the function binding
i It's all worth it
4 That is, after the cycle, finally i The value of , This is because
Python The closure of is delayed binding , This means that the value of the variable used in the closure , It is obtained by querying when an internal function is called .
The right way to use it is to put i The value of is passed as a parameter ：
def create_multipliers(): return [lambda x,i=i: x * i for i in range(5)] s = create_multipliers() for multiplier in s: print(multiplier(2)) # 0, 2, 4, 6, 8 Copy code
We use default parameters to pass
i, As with closures, the default parameter is bound to
__defaults__ Attribute .
print([f.__defaults__ for f in s]) # [(0,), (1,), (2,), (3,), (4,)] Copy code
PS： More technical dry goods , Quick attention 【 official account | xingzhe_ai】, Discuss it with the traveler ！