zoukankan      html  css  js  c++  java
  • Python-闭包详解

    在函数编程中经常用到闭包。闭包是什么,它是怎么产生的及用来解决什么问题呢。给出字面的定义先:闭包是由函数及其相关的引用环境组合而成的实体(即:闭包=函数+引用环境)(想想Erlang的外层函数传入一个参数a, 内层函数依旧传入一个参数b, 内层函数使用a和b, 最后返回内层函数)。这个从字面上很难理解,特别对于一直使用命令式语言进行编程的程序员们。本文将结合实例代码进行解释。
    函数是什么
    地球人都知道:函数只是一段可执行代码,编译后就“固化”了,每个函数在内存中只有一份实例,得到函数的入口点便可以执行函数了。在函数式编程语言中,函 数是一等公民(First class value:第一类对象,我们不需要像命令式语言中那样借助函数指针,委托操作函数),函数可以作为另一个函数的参数或返回值,可以赋给一个变量。函数可 以嵌套定义,即在一个函数内部可以定义另一个函数,有了嵌套函数这种结构,便会产生闭包问题。如:

    复制代码
    >>> def ExFunc(n):
         sum=n
         def InsFunc():
                 return sum+1
         return InsFunc

    >>> myFunc=ExFunc(10)
    >>> myFunc()
    11
    >>> myAnotherFunc=ExFunc(20)
    >>> myAnotherFunc()
    21
    >>> myFunc()
    11
    >>> myAnotherFunc()
    21
    >>>
    复制代码

    在这段程序中,函数InsFunc是函数ExFunc的内嵌函数,并且是ExFunc函数的返回值。我们注意到一个问题:内嵌函数InsFunc中 引用到外层函数中的局部变量sum,IronPython会这么处理这个问题呢?先让我们来看看这段代码的运行结果。当我们调用分别由不同的参数调用 ExFunc函数得到的函数时(myFunc(),myAnotherFunc()),得到的结果是隔离的,也就是说每次调用ExFunc函数后都将生成并保存一个新的局部变量sum。其实这里ExFunc函数返回的就是闭包。

    引用环境
    按照命令式语言的规则,ExFunc函数只是返回了内嵌函数InsFunc的地址,在执行InsFunc函数时将会由于在其作用域内找不到sum变量而出 错。而在函数式语言中,当内嵌函数体内引用到体外的变量时,将会把定义时涉及到的引用环境和函数体打包成一个整体(闭包)返回。现在给出引用环境的定义就 容易理解了:引用环境是指在程序执行中的某个点所有处于活跃状态的约束(一个变量的名字和其所代表的对象之间的联系)所组成的集合。闭包的使用和正常的函 数调用没有区别。

    由于闭包把函数和运行时的引用环境打包成为一个新的整体,所以就解决了函数编程中的嵌套所引发的问题。如上述代码段中,当每次调用ExFunc函数 时都将返回一个新的闭包实例,这些实例之间是隔离的,分别包含调用时不同的引用环境现场。不同于函数,闭包在运行时可以有多个实例,不同的引用环境和相同 的函数组合可以产生不同的实例。

     一,定义
    python中的闭包从表现形式上定义(解释)为:如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure).这个定义是相对直白的,好理解的,不像其他定义那样学究味道十足(那些学究味道重的解释,在对一个名词的解释过程中又充满了一堆让人抓狂的其他陌生名词,不适合初学者)。下面举一个简单的例子来说明。

    复制代码
    >>>def addx(x):  
    >>>    def adder(y): return x + y  
    >>>    return adder  
    >>> c =  addx(8)  
    >>> type(c)  
    <type 'function'>  
    >>> c.__name__  
    'adder'  
    >>> c(10)  
    18  
    复制代码

    结合这段简单的代码和定义来说明闭包:
    如果在一个内部函数里:adder(y)就是这个内部函数,
    对在外部作用域(但不是在全局作用域)的变量进行引用:x就是被引用的变量,x在外部作用域addx里面,但不在全局作用域里,
    则这个内部函数adder就是一个闭包。

    再稍微讲究一点的解释是,闭包=函数块+定义函数时的环境,adder就是函数块,x就是环境,当然这个环境可以有很多,不止一个简单的x。

    二,使用闭包注意事项
    1,闭包中是不能修改外部作用域的局部变量的

    复制代码
    >>> def foo():  
    ...     m = 0  
    ...     def foo1():  
    ...         m = 1  
    ...         print m  
    ...  
    ...     print m  
    ...     foo1()  
    ...     print m  
    ...  
    >>> foo()  
    0  
    1  
    0  
    复制代码

    从执行结果可以看出,虽然在闭包里面也定义了一个变量m,但是其不会改变外部函数中的局部变量m。

    2,以下这段代码是在python中使用闭包时一段经典的错误代码

    复制代码
    def foo():  
        a = 1  
        def bar():  
            a = a + 1  
            return a  
        return bar  
    复制代码

    这段程序的本意是要通过在每次调用闭包函数时都对变量a进行递增的操作。但在实际使用时

    复制代码
    >>> c = foo()  
    >>> print c()  
    Traceback (most recent call last):  
      File "<stdin>", line 1, in <module>  
      File "<stdin>", line 4, in bar  
    UnboundLocalError: local variable 'a' referenced before assignment  
    复制代码

    这是因为在执行代码 c = foo()时,python会导入全部的闭包函数体bar()来分析其的局部变量,python规则指定所有在赋值语句左面的变量都是局部变量,则在闭包bar()中,变量a在赋值符号"="的左面,被python认为是bar()中的局部变量。再接下来执行print c()时,程序运行至a = a + 1时,因为先前已经把a归为bar()中的局部变量,所以python会在bar()中去找在赋值语句右面的a的值,结果找不到,就会报错。解决的方法很简单

    复制代码
    def foo():  
        a = [1]  
        def bar():  
            a[0] = a[0] + 1  
            return a[0]  
        return bar  
    复制代码

    只要将a设定为一个容器就可以了。这样使用起来多少有点不爽,所以在python3以后,在a = a + 1 之前,使用语句nonloacal a就可以了,该语句显式的指定a不是闭包的局部变量。

    3,还有一个容易产生错误的事例也经常被人在介绍python闭包时提起,我一直都没觉得这个错误和闭包有什么太大的关系,但是它倒是的确是在python函数式编程是容易犯的一个错误,我在这里也不妨介绍一下。先看下面这段代码

    for i in range(3):  
        print i  

    在程序里面经常会出现这类的循环语句,Python的问题就在于,当循环结束以后,循环体中的临时变量i不会销毁,而是继续存在于执行环境中。还有一个python的现象是,python的函数只有在执行时,才会去找函数体里的变量的值。

    复制代码
    flist = []  
    for i in range(3):  
        def foo(x): print x + i  
        flist.append(foo)  
    for f in flist:  
        f(2)  
    复制代码

    可能有些人认为这段代码的执行结果应该是2,3,4.但是实际的结果是4,4,4。这是因为当把函数加入flist列表里时,python还没有给i赋值,只有当执行时,再去找i的值是什么,这时在第一个for循环结束以后,i的值是2,所以以上代码的执行结果是4,4,4.
    解决方法也很简单,改写一下函数的定义就可以了。

    for i in range(3):  
        def foo(x,y=i): print x + y  
        flist.append(foo)  

    三,作用
    说了这么多,不免有人要问,那这个闭包在实际的开发中有什么用呢?闭包主要是在函数式开发过程中使用。以下介绍两种闭包主要的用途。

    用途1,当闭包执行完后,仍然能够保持住当前的运行环境。
    比如说,如果你希望函数的每次执行结果,都是基于这个函数上次的运行结果。我以一个类似棋盘游戏的例子来说明。假设棋盘大小为50*50,左上角为坐标系原点(0,0),我需要一个函数,接收2个参数,分别为方向(direction),步长(step),该函数控制棋子的运动。棋子运动的新的坐标除了依赖于方向和步长以外,当然还要根据原来所处的坐标点,用闭包就可以保持住这个棋子原来所处的坐标。

    复制代码
    origin = [0, 0]  # 坐标系统原点  
    legal_x = [0, 50]  # x轴方向的合法坐标  
    legal_y = [0, 50]  # y轴方向的合法坐标  
    def create(pos=origin):  
        def player(direction,step):  
            # 这里应该首先判断参数direction,step的合法性,比如direction不能斜着走,step不能为负等  
            # 然后还要对新生成的x,y坐标的合法性进行判断处理,这里主要是想介绍闭包,就不详细写了。  
            new_x = pos[0] + direction[0]*step  
            new_y = pos[1] + direction[1]*step  
            pos[0] = new_x  
            pos[1] = new_y  
            #注意!此处不能写成 pos = [new_x, new_y],原因在上文有说过  
            return pos  
        return player  
     
    player = create()  # 创建棋子player,起点为原点  
    print player([1,0],10)  # 向x轴正方向移动10步  
    print player([0,1],20)  # 向y轴正方向移动20步  
    print player([-1,0],10)  # 向x轴负方向移动10步  
    复制代码

    输出为

    [10, 0]  
    [10, 20]  
    [0, 20]  

    用途2,闭包可以根据外部作用域的局部变量来得到不同的结果,这有点像一种类似配置功能的作用,我们可以修改外部的变量,闭包根据这个变量展现出不同的功能。比如有时我们需要对某些文件的特殊行进行分析,先要提取出这些特殊行。

    复制代码
    def make_filter(keep):  
        def the_filter(file_name):  
            file = open(file_name)  
            lines = file.readlines()  
            file.close()  
            filter_doc = [i for i in lines if keep in i]  
            return filter_doc  
        return the_filter  
    复制代码

    如果我们需要取得文件"result.txt"中含有"pass"关键字的行,则可以这样使用例子程序

    filter = make_filter("pass")  
    filter_result = filter("result.txt")  

    以上两种使用场景,用面向对象也是可以很简单的实现的,但是在用Python进行函数式编程时,闭包对数据的持久化以及按配置产生不同的功能,是很有帮助的。

    参考:http://www.cnblogs.com/Jifangliang/archive/2008/08/05/1260602.html

    参考:http://blog.csdn.net/marty_fu/article/details/7679297

    参考:https://www.cnblogs.com/JohnABC/p/4076855.html

  • 相关阅读:
    CF 142B Tprimes
    CF 231A Team
    poj 2001 Shortest Prefixes ——字典树入门
    hdu 1039 Easier Done Than Said?
    poj 2528 Mayor's posters
    hdu 1061 Rightmost Digit
    poj 2503 Babelfish
    CF271 A. Beautiful Year
    poj 2752
    CF271 B. Prime Matrix
  • 原文地址:https://www.cnblogs.com/qiangayz/p/9368752.html
Copyright © 2011-2022 走看看