• 12
  • 12
分享

一、非闭包

见过了在函数中调用函数本身,在函数内部定义一个函数:

def func():
    print('-----func被调用--------')
    def count_book():
        print('这个是计算买书方式的函数')
# func()是在外面定义的,可以直接调用func()
func()

在外面可以调用里面的函数吗?

1.png

不可以。相对于外部而言,def count_book()这个函数名是局部的,是函数内部的一个局部变量,所以在外部是访问不了函数内部的数据。

在函数内部可以访问外面的,但是在函数外面是访问不了里面的。

在外面定义个函数:

def login():
    print('登录')
def func():
    login()
    print('-----func被调用--------')
    def count_book():
        print('这个是计算买书方式的函数')
# func()是在外面定义的,可以直接调用func()
func()

2.png

在函数里面是可以调用的,因为def login()它是个全局变量。

要想在外面调用里面的def count_book()函数,有什么办法呢?

加个return,把这个函数给返回回来。接下来func()函数调用之后,它会有个返回值,返回值就是count_book()。用res接收下,接收到了之后,通过res()再调用。

调用方式一:

def login():
    print('登录')
def func():
    login()
    print('-----func被调用--------')
    def count_book():
        print('这个是计算买书方式的函数')
    return count_book
# func()是在外面定义的,可以直接调用func()
res = func()
res()

3.png

调用方式二:

def login():
    print('登录')
def func():
    login()
    print('-----func被调用--------')
    def count_book():
        print('这个是计算买书方式的函数')
    return count_book
# func()是在外面定义的,可以直接调用func()
# 方式二
func()()
# 方式一
# res = func()
# res()

4.png

以上代码不是闭包,只是符合闭包的前 2 个条件,不符合条件“内层函数对外部作用域有一个非全局的变量引用”。

二、闭包

1.闭包的概念

一个完整的闭包须满足以下 3 个条件:

  • 函数中嵌套了一个函数

  • 外层函数返回内层函数的变量名

  • 内层函数对外部作用域有一个非全局的变量引用

5.png

num = 100是外层函数里定义的一个变量,不是全局变量。

6.png

以上,这种形式的函数被称为闭包。

全局变量:变量是定义在模块里,哪个地方都可以用。

例如:

7.png

非全局变量:

8.png

不带参数的闭包:

def func():
    num = 100
    def count_book():
        print(num)
        print('这个是计算买书方式的函数')
    return count_book

带参数的闭包:

def func(num):
    def count_book():
        print(num)
        print('这个是计算买书方式的函数')
    return count_book
# func()是在外面定义的,可以直接调用func()
# 方式二
# func()()
# 方式一
res = func(2020)
res()

虽然num不是在外置函数中定义的,但是通过函数参数传进来的,传到func()的命名空间里面,print(num)在内部是可以引用到func()命名空间里面的值的。

这里的num不是全局变量,它是func()命名空间里面的一个变量,一个数据,是通过参数func(2020)传进来的。

这个也是闭包,也满足闭包的三个条件。

2.闭包的作用

实现数据的锁定,提高稳定性。

递归函数在函数调用的时候是这样的:

9.png

在一个函数里面调用自身的时候,它又有块区间放这个函数,它内部有块又调用了,它会继续在内存里面把这个函数给存起来,继续这样递归下去,非常占内存。

闭包,它没有递归。

函数调用的运行机制:

定义函数的时候,运行文件,Python 解释器从上往下执行代码,检测到def login()的时候,会在内存里面找一块地址,让函数名指向这个地址。

10.png

当你在下面再次调用这个函数的时候,Python 解释器直接运行这个内存地址里面的代码,也就是函数内部的代码。

11.png

代码从上往下运行的时候,检测到有个func(),来个地址把func()里面的代码,拿到地址里。下面调用的时候就相当于直接运行地址里面的代码了。

12.png

从上往下运行,又检测到一个函数count_book(),这个时候又会把这个函数名拿出来,然后再往下走,它直接返回了函数。

函数名拿出来之后,把这个函数名拿出来放到了这里,这个时候会给它再画出来一块地址。然后让这个count_book()函数指向这个地址,有在调用count_book()它的时候,会运行里面的代码。

这里没有调用,把count_book()这个函数名返回出来了。

count_book()这个函数名是在func()的命名空间里面。调用的时候返回到res这个地方来了。返回到全局变量里来了,通过res来接收下,res其实又指向这块内存地址了。在外面通过res调用的时候,就会运行这个内存地址里面的代码。

代码中有传入参数num,函数里面引用外层的变量num,这个变量和它放在同一个空间里面。

三、函数的__closure__属性

每个函数里面都有一个这样的属性:

res.__closure__

这个属性存储的是:当前的这个函数它里面的代码以及这个函数对外部非全局变量引用的一个数据。

13.png

当是闭包的时候,返回这样一个结果:

14.png

返回一个对象。这里存储的就是 2020。

将代码修改一下:

def func(num,b):
    def count_book():
        print(num)
        print(b)
        print('这个是计算买书方式的函数')
    return count_book
res = func(2020,'qinghan')
print(res.__closure__)

15.png

闭包函数引用的非全局变量,会存储在这个函数自身的一个__closure__属性里面,当要用的时候,直接从属性里面拿就行了。

通过这种方式实现数据锁定,提高数据的安全性。

闭包函数需要使用到外部变量,为了避免使用的外部的变量发生变化。内部所用到的外部的变量,给锁定到闭包函数自身的__closure__属性里面。

这时候外部的环境发生任何变化,对它都是没有影响的。同时也不会对外层的环境造成影响。

全局变量的时候返回 None:

16.png

如果一个闭包里面引用了全局变量,那么就不算闭包了,例如:

17.png

引用全局变量了就没办法实现数据锁定了。


作者:清菡

原文链接:https://www.cnblogs.com/qinghan123/p/14216066.html


  • 【留下美好印记】
    赞赏支持
登录 后发表评论
+ 关注

热门文章

    最新讲堂

      • 推荐阅读
      • 换一换
          •   据北京亦庄官方公众号消息,近日,北京亦庄企业国光量子成功研制出国内首款量子编解码和调制解调芯片,标志着我国再次突破量子关键技术,量子产业发展再迎新机遇。  据介绍,量子编解码和调制解调技术作为量子领域的关键技术,在量子通信、量子计算等方面具有重要作用。传统的量子编解码和调制解调设备是一个用调相器、调幅器、环形器、起偏器、法拉第镜、参量转换晶体等多种分立器械进行搭建组成的较大体积的模块装置。由于搭建的精度要达到微米级别,又要依赖于纯手工工艺,该装置对搭建人员的要求极高,通常需要专职的教授或博士生花费一两周的时间手工搭建。手工搭建装置不仅效率低,一致性也不好,极大影响量子产业很多应用的落地发展...
            0 0 591
            分享
          • 前言开发人员与测试人员齐心协力,相爱相杀, 荣辱与共,方能打造出优秀的产品。若是bug描述不知所云,bug修复仓促随意,bug管理如同儿戏,则金玉其外已是造化,败絮其中当属必然。bug描述好的描述能降低沟通成本,让人审题时如沐春风,解题时酣畅淋漓。bug描述的主体,应该包含如下部分:标题:指明所测模块,简明扼要地描述问题现象[前提条件] 说明完成测试的预设条件是什么[重现步骤] 句子简练,步骤清晰,表达无歧义[实际结果] 按照步骤执行下来,实际结果是什么;不要有主观色彩[期望结果] 正确的结果应该是什么;应该有说服力,不要唯经验论Tips:其他如所测版本,附件信息,bug优先级等,不一而足,也...
            0 0 1297
            分享
          •   一、概述  所谓回归测试就是当软件发生改变时,重新测试已经通过测试的测试区域,以验证修改的正确性及其影响。在软件开发生命周期中,软件发生改变,就会带来问题,改变可能是源于发现了错误并做了修改,也有可能是因为集成或维护阶段加入了新模块。  错误跟踪与管理系统不完善;对错误的理解不透彻,只修正了错误的外在表现,从而造成修改失败;修改还有可能产生副作用,从而导致软件未被修改的部分产生新的问题;新加入的代码还有可能对原有代码带来影响。因此,我们就必须重新测试,以便确定修改是否达到了预期的目的。同时,为了验证修改的正确性及其影响就需要进行回归测试。  回归测试作为软件生命周期的一个组成部分,在整个软...
            0 0 1030
            分享
          •   需求分析是开始测试工作的第一步,产品会先产出一个需求文档,然后会组织需求宣讲,在需求宣讲中分析需求中是否存在问题,然后宣讲结束后,通过需求文档分析测试点并且预估排期。所以对于需求的理解非常重要。  需求文档  产品经理在做完用户需求调查之后,会根据用户需求输出一份需求文档,在文档中会详细描述用户所需的功能和功能实现的效果。文档生成之后,产品经理会和开发测试一起开一个需求宣讲会,讲解需求中的内容,并且会对需求中可能存在的问题进行讨论。  需求评审  在需求宣讲的过程中,其实也需要对需求本身进行评审。需求评审可以从以下角度去进行考虑。  1.站在使用者的角度,考虑用户会遇到的各种情况,反观各种...
            0 0 731
            分享
          • 上周产品出现了一个线上 bug,我和一位同事临时通宵给做了善后处理,本来是有很清晰的处理思路,以及很熟练的处理方法,但是过程中还是出现了各种各样的问题,现做个简单总结,希望能给后续处理同类问题带来帮助。一、问题背景客户端代码有一个逻辑,判断一个文件是否是 XML 文件时,实现逻辑不严谨,没有进行充分性校验,选取的判断条件不唯一,类似我在《记一次问题分析解决的完整过程》中臆断的使用换行符来分隔字段的逻辑。因为这个逻辑的存在,如果获取 XML 文件的 URL 地址不存在,那么返回的 404 页面,也匹配上述的判断条件,结果就命中了不应该命中的流程,继续处理。在后续处理过程中,预期的数据出现了异常,...
            1 2 2125
            分享
      • 51testing软件测试圈微信