A good technical blog, let you understand Python closure quickly!

Keywords: Python Attribute less

Closure of popular understanding

Let's see what a closure is first

A closure is a function that references a free variable. This referenced free variable will exist with this function, even if it has left the environment in which it was created. So, there is another way of saying that a closure is an entity composed of a function and its associated reference environment. Closures can have multiple instances at runtime. Different reference environments and the same function combination can produce different instances.

Closure is a combination of function and its related reference environment. I think it can generalize the concept of closure. Let's take a look at the analysis

Let's take a look at the simplest example

def outer_func():
    outer_list = []
    def inner_func():
        outer_list.append(1)
        print out_list
    return inner_func

func1 = outer_func()
func1()		#[1]
func1()		#[1,1]
func2 = outer_func()
func2()		#[1]
func2()		#[1,1]

This example shows that a closure is not the same as a general function. It has a unique "environment". The outer list is called a free variable, which is neither a global variable nor a local variable.

If a name is bound in a block, it is a local variable of that block. If a name is bound at the module level, it is a global variable. (The variables of the module code block are local and global.) If a variable is used in a code block but not defined there, it is a free variable.

This feature is similar to the relationship between a class and an instance. The function outer func is like a class. Executing func1 = outer func is like creating an instance. Instance func1 can inherit the properties of a class. Here, it can also be regarded as an environment that inherits the function.

Now I'll change my writing method (it's sml). Between local and in is the environment)

local 
	outer_list = []
in
	def inner_func():
        outer_list.append(1)
        print out_list
end

The function outer func binds the environment outer list = [] with the function inner func. It does nothing more than that

Let's take a look at another closure trap to deepen our understanding

def outer_func():
    func_list = []
    
    for i in xrange(3):
        def inner_func():
            print i
        func_list.append(inner_func)
    
    return func_list

fun1,fun2,fun3 = outer_func()
fun1()	#2
fun2()	#2
fun3()	#2

Let's analyze the outer func by splitting the environment and functions

#Environment before fun1() after fun1, fun2, fun3 = outer func()
local
	func_list = [inner_func1, inner_func2,inner_func3]
        i = 2    #When environment initialization is complete, i is 2
in
	def inner_func():
    	print i
end

It can be seen that i is obviously 2, but it is slightly changed as follows

def outer_func():
    func_list = []
    
    for i in xrange(3):
        def inner_func(_i = i):    #Write default parameters
            print _i
        func_list.append(inner_func)
    
    return func_list

fun1,fun2,fun3 = outer_func()
fun1()	#0
fun2()	#0
fun3()	#2

Analyze the program above

#This shows the environment of the first inner func (func1) in func list
local
	func_list = [inner_func1, inner_func2,inner_func3]
        i = 2	#External i or 2
in
	def inner_func(_i = 0):	#For the inner func1, i=0, it can be found that the parameter i captures the value of i=0 in time as the default parameter
    	print _i
end

Why is i=0 in func1 at this time? This is because the inner func has a parameter, which can be used when the program executes func list.append (inner func),
A related "function instance" will be created, and there is a parameter "i" with default value in the function definition. Note that python can specify a variable as the default value of the function parameter,
Therefore, it will also record the value of I at the time of creation, i.e. 0

For the inner func without parameters, it only reads the value of i in the external environment, i=2, after the outer func runs completely.

Let's talk about the application of closures

Modified function

It can add additional functions, such as error detection, without changing the internal structure of existing functions.

#Closure enables the wrapper function to be executed first and then func to control the execution sequence of the function.
def func_dec( func ):
    def wrapper( *args ):
        if len(args) < 2:
            print "less argument"
        else:
            func( args )
    return wrapper

@func_dec
def mySum(*args):
    print sum( *args )

mySum(2)		#"less argument"
mySum(1,2,3)	        #6

Analysis

#Take mySum(2) as an example
local
	func = mySum
in
	def wrapper( *args ):	#args = 2
            if len(args) < 2:
                print "less argument"
            else:
                func( args )
end

In this way, the thinking should be clear

Here, mySum(2) is equivalent to func Dec (mysum) (2). Func Dec is followed by two parentheses. In fact, it can be seen that func Dec must return a function.
The reason for this trouble is that when using mySum, a function to detect the number of parameters is attached, provided that the original code of mySum is not changed. Similar to interface functions.

The above says that mySum(2) is equivalent to func μ Dec (mysum) (2), resulting in some obscure bug s
, take a look at the following example:

def counter( cls ):
    obj_list = []
    def wrapper( *args, **kwargs ):
        new_obj = cls( *args, **kwargs )
        obj_list.append( new_obj )
        print "class: %s' object number is %d" % (cls.__name__, len(obj_list) )
        return new_obj
    return wrapper


@counter
class my_cls( object ):
    STATIC_MEN = "static"
    def __init__( self, *arg, **kwargs):
        print self, arg, kwargs
        print my_cls.STATIC_MEN

my_cls()	#AttributeError: 'function' object has no attribute 'STATIC_MEN'

Why do you say 'function' object has no attribute 'static'men'?
First, determine the location of the statement error: print my_cls.STATIC_MEN

So why doesn't my CLS have the attribute static men?
This is because after using the closure (@ syntax sugar), my_cls() = counter(my_cls)()

The redirection operation should be done here (due to the syntax sugar @ counter). At this time, my CLS is no longer the original class,
During execution, the name of my CLS is pointed to counter (my CLS), i.e. wrapper function.

You can print to see the print my CLS

That's why I can directly use my_cls(), because it is no longer the original class, but the new function wrapper.
Therefore, we need to change my cls.static men to self.static men. After all, my CLS is no longer the original my CLS when it is executed

If you still want to access the static properties through my CLS, try the following methods

def counter(cls):
    obj_list = []
    @functools.wraps(cls)
    def wrapper(*args, **kwargs):
        ... ...
    return wrapper

Using functools, the wrapper is updated to make the decorated my CLS look more like the class or function before decoration.
The main principle of this process is to assign some attributes of the decorated class or function directly to the object after decoration.
Such as WRAPPER_ASSIGNMENTS (name, module and doc,) and WRAPPER_UPDATES (dict).
But this process does not change the fact that the wrapper is a function.

my_cls.__name__ == 'my_cls' and type(my_cls) is types.FunctionType

Posted by Rhysyngsun on Tue, 14 Apr 2020 00:11:43 -0700