• Python闭包


    目录

    变量作用域规则(LEGB规则)

    闭包

    闭包的用途

     (一)读取函数内部的变量

     (二)让函数内部的局部变量始终保持在内存中

    (三)总结


    变量作用域规则(LEGB规则)

    在下面的实例中,我们定义一个测试函数,它读取两个变量的值:一个是局部变量a的值,是函数的参数,一个是变量b的值,这个函数没有定义它.

    一个函数,读取一个局部变量和一个全局变量.

     出现错误并不奇怪,在上面的例子中,如果先给全局变量b赋值,也就是定义它,然后再调用f1,那它就不会出错. 

     下面看一个可能让你意想不到的例子,前两行代码与上述例子一样,然后为b赋值,再打印它的值,可是在赋值之前,第二个print失败了.

     注意:首先输出了3,这表明print(a)语句执行了,但是第二个print语句没有执行,一开始我个人觉得会打印6出来,因为有个全局变量b,而且是在print(b)之后为局部变量b赋值的,可事实是,python编译函数时,它判断b是局部变量,因为在函数内部给它赋值了.生成的字节码证实了这种判断,python会尝试从本地环境中获取b,而后调用f2(3)时,f2的定义体会获取并打印局部变量a的值,但是尝试获取局部变量b的值时,发现b没有绑定值.

    如果在函数中赋值,想让解释器把b当作全局变量,要使用global声明:

     在python中的作用域规则只需要记住LEGB规则即可,L->local,代表局部作用域,E->Enclosed,代表嵌套函数的外部函数作用域,G->global,代表全局作用域,B->Built in代表的是内置函数的关键字.

    其中,局部作用域是最高的级别,当L和G冲突时,是优先认为是局部变量,除非你使用关键字global声明,当L和E冲突时,也是优先认定是局部变量,就是内部嵌套函数的变量,除非你使用关键字nonlocal声明,该变量就是嵌套函数外部函数作用域的变量.为了形象记忆,可以记为LEG+B,腿你不爱吗(-_-)

    闭包

    可以理解为工厂函数,来料加工.

     

    但是有一种方法除外,那就是在函数的内部,再定义一个函数。 

    1. def f1():
    2. n=999
    3. def f2():
    4. print(n)
    5. return f2
    6. result = f1()
    7. result()

    在上面的代码中,函数f2就被包括在函数f1内部,这时f1内部的所有局部变量,对f2都是可见的。但是反过来就不行,f2内部的局部变量,对f1就是不可见的。这就是开头说到的,Python语言特有的作用域搜索顺序。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。

    既然f2可以读取f1中的局部变量,那么只要把f2作为返回值,我们不就可以在f1外部读取它的内部变量了吗?

    1. def f1():
    2. n=999
    3. def f2():
    4. print(n)
    5. return f2
    6. result = f1()
    7. result()

    上一部分代码中的f2函数,就是闭包。

    在上面的实例中,有一个外层函数的局部变量 n,有一个内层函数 f2,f2 里面可以访问到 n 变量,那这f2就是一个闭包。

    下面再看一下维基百科的严谨定义:

    在一些语言中,在函数中可以(嵌套)定义另一个函数时,如果内部的函数引用了外部的函数的变量,则可能产生闭包。闭包可以用来在一个函数与一组“私有”变量之间创建关联关系。在给定函数被多次调用的过程中,这些私有变量能够保持其持久性,(记忆功能)

     上面这段话实际上解释了闭包的一个定义和两个作用:

    • 定义:闭包就是能够读取外部函数内的变量的函数。(前面已经讲解过)
    • 作用1:闭包是将外层函数内的局部变量和外层函数的外部连接起来的一座桥梁。(下一部分讲解)
    • 作用2:将外层函数的变量持久地保存在内存中。(下一部分讲解)

    支持将函数当成对象使用的编程语言,一般都支持闭包。比如Python, JavaScript。

    python中一切皆是对象.

    闭包的用途

    闭包可以用在许多地方。

    维基百科的定义中已经提到的它的两个用处:① 可以读取函数内部的变量,②让这些变量的值始终保持在内存中。

     (一)读取函数内部的变量

    在第一部分中,我们讲到,有时候会为了保证命名空间的干净而把一些变量隐藏到函数内部,作为局部变量。但是由于Python中作用域的搜索顺序,函数内的变量不会被函数外的代码读取到。

    如果这时候想要函数外部的代码能够读取函数内部的变量,那么就可以使用闭包。

    闭包存在的意义就是它夹带了外部变量(私货),如果它不夹带私货,它和普通的函数就没有任何区别。同一个的函数夹带了不同的私货,就实现了不同的功能。
    其实你也可以这么理解,闭包和面向接口编程的概念很像,可以把闭包理解成轻量级的接口封装。

     (二)让函数内部的局部变量始终保持在内存中

    怎么来理解这句话呢?一般来说,函数内部的局部变量在这个函数运行完以后,就会被Python的垃圾回收机制从内存中清除掉。如果我们希望这个局部变量能够长久的保存在内存中,那么就可以用闭包来实现这个功能。

    以一个类似棋盘游戏的例子来说明。假设棋盘大小为50*50,左上角为坐标系原点(0,0),我需要一个函数,接收2个参数,分别为方向(direction),步长(step),该函数控制棋子的运动。 这里需要说明的是,每次运动的起点都是上次运动结束的终点。(小甲鱼用的是乌龟运动的例子)

    1. def create(pos=[0,0]):
    2. def go(direction, step):
    3. new_x = pos[0]+direction[0]*step
    4. new_y = pos[1]+direction[1]*step
    5. pos[0] = new_x
    6. pos[1] = new_y
    7. return pos
    8. return go
    9. player = create()
    10. print(player([1,0],10))
    11. print(player([0,1],20))
    12. print(player([-1,0],10))

    在这段代码中,player实际上就是闭包go函数的一个实例对象。

    它一共运行了三次,第一次是沿X轴前进了10来到[10,0],第二次是沿Y轴前进了20来到 [10, 20],,第三次是反方向沿X轴退了10来到[0, 20]。

    这证明了,函数create中的局部变量pos一直保存在内存中,并没有在create调用后被自动清除。

    为什么会这样呢?原因就在于create是go的父函数,而go被赋给了一个全局变量,这导致go始终在内存中,而go的存在依赖于create,因此create也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。

    这个时候,闭包使得函数的实例对象的内部变量,变得很像一个的实例对象的属性,可以一直保存在内存中,并不断的对其进行运算。

    (三)总结

    • 局部变量无法共享和长久的保存,而全局变量可能造成变量污染,闭包既可以长久的保存变量又不会造成全局污染。
    • 闭包使得函数内局部变量的值始终保持在内存中,不会在外层函数调用后被自动清除。
    • 当外层函数返回了内层函数后,外层函数的局部变量还被内层函数引用
    • 带参数的装饰器,那么一般都会生成闭包。
    • 闭包在爬虫以及web应用中都有很广泛的应用。

  • 相关阅读:
    2023年腾讯云双11活动云服务器价格表
    【Linux服务端搭建及使用】
    python基于django的网上人才招聘系统
    使用uniapp设置tabbar的角标和移除tabbar的角标
    Python 实现Tracert追踪TTL值
    液压比例阀放大器比例控制器比例阀放大板
    Java设计模式-单例模式
    大数据之Spark(二)
    关于pycharm打开时一直加载中的解决办法
    由于找不到vcruntime140_1.dll文件的解决方法,带你了解vcruntime140_1.dll这个dll
  • 原文地址:https://blog.csdn.net/qq_43460068/article/details/128043755