您的位置:首页 > 编程语言 > Python开发

【Python】闭包中一个不易察觉的陷阱

2015-02-27 15:52 197 查看
Python语言是支持函数式编程的,我们可以在一个函数的函数体中定义另一个完整的函数,甚至返回这个函数.在函数内部定义的函数和外部定义的函数是相同的,唯一的区别就是在函数内部定义的函数是不能被外部访问的.

下面的一段代码定义了两个函数f() 和 g(),在函数f()中把g()作为返回值返回,这里的g你可以参考C语言中函数的指针来进行理解,它们两个几乎在语义和用法上几乎完全相同(C语言中函数名即为指向函数的指针).

def g():
    print 'g()...'

def f():
    print 'f()...'
    return g


我们把函数g()的定义移到f()内部,这样除了f()之外的区域就无法调用g(),代码如下:

def f():
    print 'f()...'
    def g():
        print 'g()...'
    return g


但是有一种情况我们是无法把函数g()移动到函数f()外面的,请观察一下如下代码:

list = [1, 2, 3, 4, 5]

def f(list):
    def g():
        return sum(list)
    return g
list是一个列表,作为参数传进函数f()中,此时我们发现无法把函数g()移到函数f()的外面,因为函数g()的功能是计算list中所有元素值的和,必须依赖于函数f()的参数;如果函数g()不在函数f()里面,则无法取得数据进行计算,这样我们就引出了闭包的概念.

闭包(Closure):内层函数引用了外层函数的变量(包括它的参数),然后返回内层函数的情况,这就是闭包.

接下来就要重点介绍本文标题中提到的那个不易察觉的陷阱了.

观察如下代码:

# 希望一次返回3个函数,分别计算1x1,2x2,3x3:
def count():
    fs = []
    for i in range(1, 4):
        def f():
             return i*i
        fs.append(f)
    return fs

f1, f2, f3 = count()

print f1(), f2(), f3()


这里的count()函数是通过函数的闭包来返回一个包含三个函数的列表,分别是计算1x1的函数, 计算2x2的函数, 计算3x3的函数,那么我们分别调用执行f1()函数, f2()函数, f3()函数的执行结果是多少呢?会不会是我们期望的1 4 9 呢?您不妨自己在机器上运行一下这段代码,如果嫌麻烦的话可以看一下我的运行结果截图:



(在这里跟大家安利一下我用的IDE,伟大的Jet BRAINS公司[1]的推出的PyCharm,功能强大,有收费版和免费社区版[2],我用的就是社区版)

竟然是三个9!!! 第一次遇到这个问题其实我是拒绝滴,难道是加了特效么,我duang的一下就迷惑了






翻了翻书才知道这是Python语言的特性所致.当count()函数返回三个函数时,这三个变量所引用的变量i的值已经变成了3,因为在返回的时候三个函数并没有被调用,所以此时它们并没有及时计算它们对应的i乘以i的值,等到三个函数都返回时,然后调用三个函数,此时i的值已经为3,计算i乘以i的值自然就都是9了.

所以在返回闭包的情况下,我们一定要注意的一点就是:

返回函数千万不要引用任何一个循环变量,或者在之后会发生改变的变量.

当然对于这种情况我们还是有解决方法的.我们在内层函数f()内再定义一个内层函数g(),用这个函数的参数绑定循环变量的当前值,这样的话,无论循环变量之后如何改变,每一次循环中的循环变量i的值就都保存在了第三层函数g()中,如此得到了我们期望的输出结果.

代码如下:
#coding:utf-8
__author__ = 'chad'
# 希望一次返回3个函数,分别计算1x1,2x2,3x3:
def count():
    fs = []
    for i in range(1, 4):
        def f(j):
            def g():
                return j*j
            return g
        fs.append(f(i))
    return fs

f1, f2, f3 = count()

print f1(), f2(), f3()


程序的执行结果如下:



如果您嫌这段代码过于臃肿,可以考虑使用lambda表达式进行缩减.

感谢大家.

[1]Jet Brains维基百科直达链接:zh.wikipedia.org/zh-cn/JetBrains

[2]PyCharm Community Edition Version 4.04 下载链接:https://www.jetbrains.com/pycharm/download/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: