zoukankan      html  css  js  c++  java
  • 理解Python中的闭包

    1.定义

      闭包是函数式编程的一个重要的语法结构,函数式编程是一种编程范式 (而面向过程编程和面向对象编程也都是编程范式)。在面向过程编程中,我们见到过函数(function);在面向对象编程中,我们见过对象(object)。函数和对象的根本目的是以某种逻辑方式组织代码,并提高代码的可重复使用性(reusability)。闭包也是一种组织代码的结构,它同样提高了代码的可重复使用性。
      不同编程语言实现闭包的方式是不同的,python中闭包从表现形式上看,如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure)。
    举个例子:

    def outer(x):
        def inner(y):
            return x + y
        return inner 
    
    • 1
    • 2
    • 3
    • 4
    • 5

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

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

      在函数outer中定义了一个inner函数,inner函数访问外部函数outer的(参数)变量,并且把inner函数作为返回值返回给outer函数。

    a = outer(2)
    print('function:',a) 
    print('result:',a(3))
    • 1
    • 2
    • 3

      上面的代码中a就是一个函数,代码的执行结果为:

    这里写图片描述

      从结果我们不难看出,a是函数inner而不是outer,这个有点绕,但是并不难理解,因为return回来的是inner函数。

    print('a.func_name',a.func_name)
    • 1

    输出结果为:这里写图片描述

      调用函数a,得到的结果是传入参数的值相加。

      上面的和这句是一样的:print('result:',outer(2)(3))

    2.使用闭包注意的地方

    2.1闭包无法修改外部函数的局部变量

    这里写图片描述

    如果innerFunc可以修改x的值的话,x的值前后会发生变化,但结果是:

    这里写图片描述

    在innerFunc中x的值发生了改变,但是在outerFunc中x的值并未发生变化。

    再来看一个例子

    2.2闭包无法直接访问外部函数的局部变量

    def outer():
        x = 5 
        def inner(): #上面一行的x相对inner函数来说是函数外的局部变量(非全局变量)
            x *= x
            return x
        return inner
    
    outer()()
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    运行会报错:
    这里写图片描述

    解决的方法:
      1.在python3之前没有直接的解决方法,只能间接地通过容器类型来解决,因为容器类型不是存放在栈空间的,inner函数可以访问到。

    def outer():
        x = [5] 
        def inner(): 
            x[0] *= x[0]
            return x[0]
        return inner 
    
    print(outer()())  #25
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

      2.python3通过nonlocal关键字来解决,该语句显式的指定a不是闭包的局部变量。

    def outer():
        x = 5 
        def inner(): 
            nonlocal x #把x声明为非局部变量
            x *= x
            return x
        return inner 
    
    print(outer()())
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    2.3python循环中不包含域的概念

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

    for i in range(3):  
    print i 
    
    • 1
    • 2
    • 3

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

    flist = []  
    for i in range(3):   
        def foo(x): print x + i   
    for f in flist:   
        f(2) 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

      可能有些人认为这段代码的执行结果应该是2,3,4.但是实际的结果是4,4,4。loop在python中是没有域的概念的,flist在像列表中添加func的时候,并没有保存i的值,而是当执行f(2)的时候才去取,这时候循环已经结束,i的值是2,所以结果都是4。
      解决方法也很简单,改写一下函数的定义就可以了。

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

    3.闭包的作用

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

    用途1:当闭包执行完后,仍然能够保持住当前的运行环境。

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

    origin = [0, 0] 
    legal_x = [0, 50]  
    legal_y = [0, 50] 
    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],因为参数变量不能被修改,而pos[]是容器类的解决方法 
            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步 
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    输出为:

     [10, 0] 
     [10, 20]  
     [0, 20] 
    • 1
    • 2
    • 3

    用途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 
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

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

    filter = make_filter("pass") filter_result = filter("result.txt") 
    
    • 1
    • 2

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

  • 相关阅读:
    我的知识库(4) java获取页面编码(Z)
    知识库(3)JAVA 正则表达式 (超详细)
    The Struts dispatcher cannot be found. This is usually caused by using Struts tags without the associated filter. Struts
    某人总结的《英语听力的技巧 》,挺搞的
    我的知识库(5)java单例模式详解
    构建可扩展程序
    SerialPort (RS232 Serial COM Port) in C# .NET
    Python学习笔记——String、Sequences
    UI题目我的答案
    jQuery学习系列学会操纵Form表单元素(1)
  • 原文地址:https://www.cnblogs.com/qiumingcheng/p/14186372.html
Copyright © 2011-2022 走看看