在 Python 中学习闭包前,需要先了解以下两点知识:
1️⃣局部作用域的生命周期。
每次调用函数时,都会创建一个新的局部作用域。
函数执行完毕后,该作用域就会被销毁,局部变量随之释放。
测试代码如下:
def test():
num = 100
num += 10
print(num)
test() # 110
test() # 110
test() # 110
# 每次调用 test() 函数,num 都会从新生成,不会保存上一次的值
2️⃣内层函数访问外层变量。
内层函数可以访问到外层函数作用域中的变量。
访问外层变量不用nonlocal,修改外层变量时要使用nonlocal。
def outer():
num = 10
def inner():
print(num)
inner()
outer()
如下的写法,用于验证:理论上num变量应该在outer函数调用完销毁,但实际并没有。
def outer():
num = 10
def inner():
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进行读取和修改。
现在我们观察此时的代码形式,完全符合上述的闭包产生条件,所以此时就出现了闭包。
def outer():
num = 10
def inner():
nonlocal num
num += 1
print(num)
return inner # 注意:这里直接返回函数,而不是函数的执行结果
result = outer()
result() # 11
result() # 12
result() # 13
result() # 14
result() # 15
外层变量会被保存到 闭包单元(cell)中,例如下面的代码中,那些被inner函数所使用到的outer函数中的局部变量,会被封存在 闭包单元(cell)中,这些cell组成一个__closure元组,保存在了inner函数上。
def outer():
num = 10
print(id(num)) # 140723580982472
def inner():
nonlocal num
num += 1
print(num)
return inner # 注意:这里直接返回函数,而不是函数的执行结果
result = outer()
print(result.__closure__) # __closure__的值是一个元组,其中保存着被inner函数所“封存”的外层变量
print(result.__closure__[0].cell_contents) # 10
print(id(result.__closure__[0].cell_contents)) # 140723580982472
print()
result()
print(result.__closure__) # __closure__的值是一个元组,其中保存着被inner函数所“封存”的外层变量
print(result.__closure__[0].cell_contents) # 11
print(id(result.__closure__[0].cell_contents)) # 140723580982504
print()
result()
print(result.__closure__) # __closure__的值是一个元组,其中保存着被inner函数所“封存”的外层变量
print(result.__closure__[0].cell_contents) # 12
print(id(result.__closure__[0].cell_contents)) # 140723580982536
思考:内层函数inner会保存外层函数outer中所有的数据吗?不会,只会保存inner中所用到的。
def outer():
num = 10
# print(id(num)) # 140723580982472
msg = 'hello'
print(msg)
def inner():
nonlocal num
num += 1
print(num)
print(msg)
return inner # 注意:这里直接返回函数,而不是函数的执行结果
result = outer()
# 验证方法:进行注释和不注释inner函数内 print(msg) 这行代码后,看下面的打印结果
print(result.__closure__)
1️⃣每次获得一个新闭包,互补影响(闭包之间是互相独立的)。
def outer():
num = 10
def inner():
nonlocal num
num += 1
print(num)
return inner
f1 = outer()
f1() # 11
f1() # 12
f1() # 13
print('-' * 10)
f2 = outer()
f2() # 11
f2() # 12
f2() # 13
2️⃣外层变量为可变对象时,仍互不影响。
def outer2():
nums = []
def inner(value):
nonlocal nums
nums.append(value)
print(nums)
return inner
f1 = 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)等高级用法的基础。
小案例:使用闭包实现对文字的美化效果。
def beauty(char, n):
def show_msg(msg):
print(char * n + msg + char * n)
return show_msg
show1 = beauty('*', 3)
show1('你好') # ***你好***
show1('Python') # ***Python***
show1 = beauty('@', 6)
show1('你好') # @@@@@@你好@@@@@@
show1('Python') # @@@@@@Python@@@@@@
⬜️理解成本较高:对初学者不太友好,滥用会让代码难读。
⬜️如果闭包里引用了很大的对象,又长期不释放,可能会增加内存占用。
⬜️很多场景下,其实用【类 + 实例属性】会更清晰,闭包不一定是最优解。
例如:将上述的文字美化效果的案例,用类实现:
class Beauty:
def __init__(self, char, n):
self.char = char
self.n = n
def show_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@@@@@@
🔥BuildAdmin是一个永久免费开源,无需授权即可商业使用,且使用了流行技术栈快速创建商业级后台管理系统。