测开之函数进阶· 第6篇《闭包》

坚持原创输出,点击蓝字关注我吧

作者:清菡
博客:oschina、云+社区、知乎等各大平台都有。

由于微信公众号推送改为了信息流的形式,防止走丢,请给加个星标 ⭐,你就可以第一时间接收到本公众号的推送!

目录

  • 一、非闭包
  • 二、闭包
    • 1.闭包的概念
    • 2.闭包的作用
  • 三、函数的__closure__属性

一、非闭包

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

def func():
    print('-----func被调用--------')
    def count_book():
        print('这个是计算买书方式的函数')

# func()是在外面定义的,可以直接调用func()
func()

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

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

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

在外面定义个函数:

def login():
    print('登录')

def func():
    login()
    print('-----func被调用--------')
    def count_book():
        print('这个是计算买书方式的函数')

# func()是在外面定义的,可以直接调用func()
func()

在函数里面是可以调用的,因为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()

调用方式二:

def login():
    print('登录')


def func():
    login()
    print('-----func被调用--------')

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


# func()是在外面定义的,可以直接调用func()

# 方式二
func()()

# 方式一
# res = func()
# res()

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

二、闭包

1.闭包的概念

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

  • 函数中嵌套了一个函数
  • 外层函数返回内层函数的变量名
  • 内层函数对外部作用域有一个非全局的变量引用

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

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

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

例如:

非全局变量:

不带参数的闭包:

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.闭包的作用

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

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

递归调用原理图

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

闭包,它没有递归。

函数调用的运行机制:

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

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

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

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

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

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

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

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

三、函数的__closure__属性

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

res.__closure__

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

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

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

将代码修改一下:

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

res = func(2020,'qinghan')
print(res.__closure__)

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

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

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

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

全局变量的时候返回 None:

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

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


公众号清菡软件测试首发,更多原创文章:清菡软件测试 115+原创文章,欢迎关注、交流,禁止第三方擅自转载。