欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

一篇好的技术博文,快速让你通俗理解Python闭包!

程序员文章站 2022-05-04 11:24:48
通俗理解闭包 先来看看什么是闭包吧 闭包是引用了*变量的函数。这个被引用的*变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。 这句话闭 ......

通俗理解闭包

先来看看什么是闭包吧

闭包是引用了*变量的函数。这个被引用的*变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。

这句话闭包是由函数和与其相关的引用环境组合而成的实体,我觉得已经能概括闭包的概念了。下面看看分析

一篇好的技术博文,快速让你通俗理解Python闭包!


先看一个最简单的例子

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]

这个例子说明闭包与一般的函数不一样,他拥有的“环境”是独一份的。其中的outer_list称为*变量,既不是全局变量又不是本地变量。

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.

这种特性类似 类与实例 的关系,函数outer_func就像是一个类,执行func1 = outer_func就像是创建了一个实例,而实例func1能够继承类的属性,这里也可以看作是继承oucter_func的环境

下面我换一种写法(这种写法是sml的写法。local与in之间就是环境)

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

函数outer_func将环境outer_list = []与函数inner_func捆绑在一起,它的作用仅此而已


下面为了加深理解,我们再看一个闭包陷阱

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

我们再来通过拆分环境和函数来分析outer_func

#执行fun1,fun2,fun3 = outer_func()之后,执行fun1()之前的环境
local
	func_list = [inner_func1, inner_func2,inner_func3]
        i = 2    #"环境初始化"完成之后,i就是2
in
	def inner_func():
    	print i
end

这样可以看出i明显是2,但下面稍加改动

def outer_func():
    func_list = []
    
    for i in xrange(3):
        def inner_func(_i = i):    #写入默认参数
            print _i
        func_list.append(inner_func)
    
    return func_list

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

分析上面的程序

#这里展示func_list中 第一个 inner_func(func1)的环境
local
	func_list = [inner_func1, inner_func2,inner_func3]
        i = 2	#外部的i还是2
in
	def inner_func(_i = 0):	#对于inner_func1,_i=0,这里可以发现形参_i及时捕获i=0时的值,当作默认参数
    	print _i
end

为什么这时候func1中的i=0呢?这是因为inner_func有了参数_i,它能在程序执行func_list.append(inner_func)的时候,
会创建相关”函数实例“,而该函数定义中有一个带默认值的形参_i,注意python可以指定一个变量作为函数参数的默认值
因此它会在创建的时候也记录下i此时的值,即0.

而不带参数的inner_func,它只会在outer_func完全运行结束之后,读取外部环境中i的值,即i=2。


下面说说闭包的应用

修饰函数

能在不改动已有函数内部构造的同时,添加额外功能,如检错功能。

#闭包使得先执行wrapper函数再执行func,可以控制函数执行的先后。
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

分析一下

#以mysum(2)为例子
local
	func = mysum
in
	def wrapper( *args ):	#args = 2
            if len(args) < 2:
                print "less argument"
            else:
                func( args )
end

这样看思路应该清晰不少

这里的 mysum(2) 等价于 func_dec( mysum )(2),func_dec后面接了2个括号,其实也可以看出func_dec必定返回一个函数。
之所以搞得这么麻烦,就是为了让使用mysum的时候附带一个检测参数个数的功能,前提是不改变mysum原有代码。类似接口函数。


上面的说mysum(2) 等价于 func_dec( mysum )(2),由此会产生一些隐晦的bug
,看看下面的例子:

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'

为什么会说'function' object has no attribute 'static_men'呢?
首先确定语句出错的位置:print my_cls.static_men

那为什么my_cls不存在属性static_men呢?
这是因为使用闭包后(@语法糖),my_cls() = counter(my_cls)()

这里应该被做了类似重定向的操作(因为语法糖@counter的缘故), 此时my_cls不再是原来的class,
执行的时候my_cls这个名字被指向了counter(my_cls), 即wrapper函数。

可以打印看看print my_cls.__name__ #显示wrapper

这也是为什么能直接使用my_cls()的原因,因为它已经不再是原来的类,而是新的函数wrapper。
因此需要将my_cls.static_men修改为self.static_men,毕竟执行的时候my_cls已经不再是原来的my_cls了

要是还想通过my_cls访问静态属性,尝试以下方法

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

对wrapper使用functools进行了一次包裹更新,使经过装饰的my_cls看起来更像装饰之前的类或者函数。
该过程的主要原理就是将被装饰类或者函数的部分属性直接赋值到装饰之后的对象
如wrapper_assignments(namemodule and doc, )和wrapper_updates(dict)等。
但是该过程不会改变wrapper是函数这样一个事实。

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