深入理解Python闭包概念

闭包并不只是一个Python中的概念,在函数式编程语言中应用较为广泛。理解python中的闭包一方面是能够正确的使用闭包,另一方面可以好好体会和思考闭包的设计思想。

1.概念介绍

首先看一下维基上对闭包的解释:

在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。

简单来说就是一个函数定义中引用了函数外定义的变量,并且该函数可以在其定义环境外被执行。这样的一个函数我们称之为闭包。实际上闭包可以看做一种更加广义的函数概念。因为其已经不再是传统意义上定义的函数。

根据我们对编程语言中函数的理解,大概印象中的函数是这样的:

程序被加载到内存执行时,函数定义的代码被存放在代码段中。函数被调用时,会在栈上创建其执行环境,也就是初始化其中定义的变量和外部传入的形参以便函数进行下一步的执行操作。当函数执行完成并返回函数结果后,函数栈帧便会被销毁掉。函数中的临时变量以及存储的中间计算结果都不会保留。下次调用时唯一发生变化的就是函数传入的形参可能会不一样。函数栈帧会重新初始化函数的执行环境。

C++中有static关键字,函数中的static关键字定义的变量独立于函数之外,而且会保留函数中值的变化。函数中使用的全局变量也有类似的性质。

但是闭包中引用的函数定义之外的变量是否可以这么理解呢?但是如果函数中引用的变量既不是全局的,也不是静态的(python中没有这个概念)。应该怎么正确的理解呢?

建议先参考另一篇博文(Python  UnboundLocalError和NameError错误根源解析 ),了解一下变量可见性和绑定相关的概念非常有必要。

2.闭包初探

为了说明闭包中引用的变量的性质,可以看一下下面的这个例子:

def outer_func():
    loc_list = []
    def inner_func(name):
        loc_list.append(len(loc_list) + 1)
        print '%s loc_list = %s' %(name, loc_list)
    return inner_func

clo_func_0 = outer_func()
clo_func_0('clo_func_0')
clo_func_0('clo_func_0')
clo_func_0('clo_func_0')
clo_func_1 = outer_func()
clo_func_1('clo_func_1')
clo_func_0('clo_func_0')
clo_func_1('clo_func_1')

程序的运行结果:

clo_func_0 loc_list = [1]
clo_func_0 loc_list = [1, 2]
clo_func_0 loc_list = [1, 2, 3]
clo_func_1 loc_list = [1]
clo_func_0 loc_list = [1, 2, 3, 4]
clo_func_1 loc_list = [1, 2]

从上面这个简单的例子应该对闭包有一个直观的理解了。运行的结果也说明了闭包函数中引用的父函数中local variable既不具有C++中的全局变量的性质也没有static变量的行为。

在python中我们称上面的这个loc_list为闭包函数inner_func的一个自由变量(free 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.

在这个例子中我们至少可以对闭包中引用的自由变量有如下的认识:

闭包中的引用的自由变量只和具体的闭包有关联,闭包的每个实例引用的自由变量互不干扰。

一个闭包实例对其自由变量的修改会被传递到下一次该闭包实例的调用。

由于这个概念理解起来并不是那么的直观,因此使用的时候很容易掉进陷阱。

3.闭包陷阱

下面先来看一个例子:

def my_func(*args):
    fs = []
    for i in xrange(3):
        def func():
            return i * i
        fs.append(func)
    return fs

fs1, fs2, fs3 = my_func()
print fs1()
print fs2()
print fs3()

上面这段代码可谓是典型的错误使用闭包的例子。程序的结果并不是我们想象的结果0,1,4。实际结果全部是4。

这个例子中,my_func返回的并不是一个闭包函数,而是一个包含三个闭包函数的一个list。这个例子中比较特殊的地方就是返回的所有闭包函数均引用父函数中定义的同一个自由变量。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/0f37c84842a32880f62bf807825c52d1.html