• 13、闭包


    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表达式进行缩减.

    感谢大家.

  • 相关阅读:
    Linux strace命令使用详解
    Ubuntu 给应用程序添加桌面图标(本文以Eclipse为例)
    Linux(Ubuntu/Debian/CentOS/RedHat)下交叉编译boost库
    Ubuntu下安装RabbbitVCS(图形化svn管理工具)- Ubuntu也有TortoiseSVN
    Linux下编译C++程序遇到错误:undefined reference to `*::*
    Linux系统(Ubuntu/Debian/RedHat/CentOS)超级简单的samba配置文件smb.conf
    Makefile常用万能模板(包括静态链接库、动态链接库、可执行文件)
    Ubuntu/Debian交叉编译安装ARM平台版本的ffmpeg
    apue编程之参考du代码利用递归写的一个简单的du命令的源代码
    linux 编译kernel与svn版本冲突解决方法 [drivers/gpu/mali/mali/common/mali_kernel_core.o] 错误 1
  • 原文地址:https://www.cnblogs.com/lixiaofou/p/8269919.html
Copyright © 2020-2023  润新知