❝Python入门第十九课,主要是学习了闭包,闭包初学者较难理解,闭包实质是函数嵌套并引用外部变量,返回内部函数,形成保留外部状态的作用域结构。
前置知识
在 Python 中学习闭包前,需要先了解以下两点知识:
1、局部作用域的生命周期。
每次调用函数时,都会创建一个新的局部作用域。
函数执行完毕后,该作用域就会被销毁,局部变量随之释放。
测试代码如下:
deftest(): num = 100 num += 10 print(num)test() # 110test() # 110test() # 110# 每次调用 test() 函数,num 都会从新生成,不会保存上一次的值
2、内层函数访问外层变量。
内层函数可以访问到外层函数作用域中的变量。
访问外层变量不用nonlocal,修改外层变量时要使用nonlocal。
defouter(): num = 10definner(): print(num) inner()outer()
如下的写法,用于验证:理论上num变量应该在outer函数调用完销毁,但实际并没有。
defouter(): num = 10definner(): print(num)return inner # 注意:这里直接返回函数,而不是函数的执行结果result = outer() # outer函数执行print(type(result), result) # <class 'function'> <function outer.<locals>.inner at 0x00000147D3267880>result() # 正常返回num的值:10
什么是闭包
闭包 = 内层函数 + 被内层函数引用的外层变量。
产生闭包的三个条件:
❶ 必须有函数嵌套。
❷ 内层函数使用了外层函数的变量。
❸ 外层函数返回内层函数。
闭包的基本形式
在上述代码的基础上,我们做出这样的调整:在inner函数中不仅要打印num,还要修改num(修改时记得机上nonlocal num)。
最终发现:num的值居然还可以一直增加,所以截止目前,证明了一件事:本应该随着outer函数调用结束而释放或销毁的变量num,并没被释放或销毁,并且inner函数依然可以对num进行读取和修改。
现在我们观察此时的代码形式,完全符合上述的闭包产生条件,所以此时就出现了闭包。
defouter(): num = 10definner():nonlocal num num += 1 print(num)return inner # 注意:这里直接返回函数,而不是函数的执行结果result = outer()result() # 11result() # 12result() # 13result() # 14result() # 15
闭包是如何保存外层变量的?
外层变量会被保存到 闭包单元(cell)中,例如下面的代码中,那些被inner函数所使用到的outer函数中的局部变量,会被封存在 闭包单元(cell)中,这些cell组成一个__closure元组,保存在了inner函数上。
defouter(): num = 10 print(id(num)) # 140723580982472definner():nonlocal num num += 1 print(num)return inner # 注意:这里直接返回函数,而不是函数的执行结果result = outer()print(result.__closure__) # __closure__的值是一个元组,其中保存着被inner函数所“封存”的外层变量print(result.__closure__[0].cell_contents) # 10print(id(result.__closure__[0].cell_contents)) # 140723580982472print()result()print(result.__closure__) # __closure__的值是一个元组,其中保存着被inner函数所“封存”的外层变量print(result.__closure__[0].cell_contents) # 11print(id(result.__closure__[0].cell_contents)) # 140723580982504print()result()print(result.__closure__) # __closure__的值是一个元组,其中保存着被inner函数所“封存”的外层变量print(result.__closure__[0].cell_contents) # 12print(id(result.__closure__[0].cell_contents)) # 140723580982536
思考:内层函数inner会保存外层函数outer中所有的数据吗?不会,只会保存inner中所用到的。
defouter(): num = 10# print(id(num)) # 140723580982472 msg = 'hello' print(msg)definner():nonlocal num num += 1 print(num) print(msg)return inner # 注意:这里直接返回函数,而不是函数的执行结果result = outer()# 验证方法:进行注释和不注释inner函数内 print(msg) 这行代码后,看下面的打印结果print(result.__closure__)
注意点
1、每次获得一个新闭包,互补影响(闭包之间是互相独立的)。
defouter(): num = 10definner():nonlocal num num += 1 print(num)return innerf1 = outer()f1() # 11f1() # 12f1() # 13print('-' * 10)f2 = outer()f2() # 11f2() # 12f2() # 13
2、外层变量为可变对象时,仍互不影响。
defouter2(): nums = []definner(value):nonlocal nums nums.append(value) print(nums)return innerf1 = outer2()f1(10) # [10]f1(20) # [10, 20]f1(30) # [10, 20, 30]print('-' * 10)f2 = outer2()f2(100) # [100]f2(200) # [100, 200]
闭包的优点
❏ 可以“记住”状态:不用全局变量,也不用写类,就能在多次调用之间保存数据。
❏ 可以做“配置过的函数”:先传一部分参数,把环境固定住,得到一个定制版函数。
❏ 可以实现简单的“数据隐藏”:外层变量对外不可见,只能通过内层函数访问。
❏ 是装饰器(decorator)等高级用法的基础。
小案例:使用闭包实现对文字的美化效果。
defbeauty(char, n):defshow_msg(msg): print(char * n + msg + char * n)return show_msgshow1 = beauty('*', 3)show1('你好') # ***你好***show1('Python') # ***Python***show1 = beauty('@', 6)show1('你好') # @@@@@@你好@@@@@@show1('Python') # @@@@@@Python@@@@@@
闭包的缺点
❏ 理解成本较高:对初学者不太友好,滥用会让代码难读。
❏ 如果闭包里引用了很大的对象,又长期不释放,可能会增加内存占用。
❏ 很多场景下,其实用【类 + 实例属性】会更清晰,闭包不一定是最优解。
例如:将上述的文字美化效果的案例,用类实现:
classBeauty:def__init__(self, char, n): self.char = char self.n = ndefshow_msg(self, msg): print(self.char * self.n + msg + self.char * self.n)b1 = Beauty('*', 3)b1.show_msg('Python') # ***Python***b2 = Beauty('@', 6)b2.show_msg('Python') # @@@@@@Python@@@@@@