In the same way, husbands should love their wives as they do their own bodies. He who loves his wife loves himself. For no one ever hates his own body, but he nourishes and tenderly cares for it, just as Christ does for the church, because we are members of his body. "For this reason a man will leave his father and mother and be joined to his wife,and the two will become on flesh." This is a great mystery, and I am applying it to Christ and the church. Each of you, however, should love his wife as himself, and a wife should respect her husband. (EPHESIANS 6:28-33)

函数(5)

“闭包”是一个很酷的名词,不是吗?你听说过“烧包”、“豆包”、“脓包”等词语,“闭包”跟它们比起来,更有点神秘色彩。

什么是闭包

在数学上,有“闭包”,但此处讨论的是计算机高级语言中的“闭包”,维基百科上有这样的定义:

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

闭包的概念出现于60年代,最早实现闭包的程序语言是Scheme。之后,闭包被广泛使用于函数式编程语言如ML语言和LISP。很多命令式程序语言也开始支持闭包。

上面的定义是很严格的,也是比较难理解的。所以,要用简单的例子来说明。

毋庸置疑,下面这段程序是能够顺利运行的。

a = 3

def foo():
    print a    #Python 3: print(a)

foo()

a = 3定义的变量在函数里面能够被调用,但是反过来,如下所示:

def foo():
    a = 3

print a    #Python 3: print(a)

这段程序会毋庸置疑地报错了。其原因就可以用变量的作用域来解释了,详细见命名空间

在函数foo()里面可以直接使用函数外面的a = 3,但是在函数foo()外面不能使用它里面的所定义的a = 3。根据作用域的关系,是合情合理的。然而,也许在某种特殊情况下,我们需要在函数外面使用函数里面的变量,怎么办?

def foo():
    a = 3
    def bar():
        return a
    return bar

f = foo()
print f()    #Python 3: print(f())
#output:
3

用上面的方式,就实现了在函数外面得到函数里面所定义的对象。这种写法的本质就是嵌套函数

在函数foo()里面,有a = 3和另外一个函数bar(),它们两个都在函数foo()的环境里面,但是,它们两个是互不统属的,所以变量a相对函数bar()是自由变量,并且在函数bar()中应用了这个自由变量——函数bar()就是我们所定义的闭包。

闭包是一个函数,并且这个函数具有以下特点:

  • 定义在另外一个函数里面(嵌套函数)
  • 引用其所在函数环境的自由变量

从上述代码的运行效果上看,通过闭包,能够在定义自由变量a = 3的环境foo()之外的地方得到该自由变量所引用的对象,或者说foo()执行完毕,但a = 3依然可以在f()bar()函数中存在,而没有被收回。所以,print f()才得到了其结果。

使用闭包

为什么要是用闭包?

如果不使用必要,也能编程,这是确认无疑的。

只不过,在某些时候,需要对事务做更高层次的抽象,这就可能用到闭包。

比如要写一个关于抛物线的函数。如不使用闭包,对于读者来讲应该能够轻易完成,现在使用闭包的方式,可以这么做。

#!/usr/bin/env python
# coding:utf-8

def parabola(a, b, c):
    def para(x):
        return a*x**2 + b*x + c
    return para

p = parabola(2, 3, 4)
print p(5)        #Python 3: print(p(5))

在上面的函数中,p = parabola(2, 3, 4)定义了一个抛物线的函数对象——状如y = 2x^2 + 3x + 4,如果要计算x = 5时,该抛物线函数的值,只需要p(5)即可。这种写法是不是让函数只用起来更简洁?

读者在学习了的有关知识之后,再回来阅读这个闭包的应用,会认识到,此处以p = parabola(2, 3, 4)的形式,就如同类中创建实例一样。可以利用上面的函数创建多个实例,也就是得到多个不同的抛物线函数对象。

这就是闭包应用的典型案例之一。

另外,装饰器,本质上就是闭包的一种应用——可以再次阅读装饰器

当然,闭包在实践中还有其它方面的应用,作为入门教程,此处不做深究。读者如果有意愿,可以去Google有关内容,有不少大神在这方面撰写了文章。


总目录   |   上节:函数(4)   |   下节:函数(6)

如果你认为有必要打赏我,请通过支付宝:[email protected],不胜感激。

results matching ""

    No results matching ""