ython快速入门学习笔记(进阶篇)十九:闭包

  • 原创
  • 作者:程序员三丰
  • 发布时间:2026-04-02 10:56
  • 浏览量:12
python入门第十九课,主要是学习了闭包,闭包初学者较难理解,闭包实质是函数嵌套并引用外部变量,返回内部函数,形成保留外部状态的作用域结构。

前置知识

在 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@@@@@@
声明:本文为原创文章,51blog.xyz和作者拥有版权,如需转载,请注明来源于51blog.xyz并保留原文链接:https://mp.51blog.xyz/article/121.html

文章归档

推荐文章

buildadmin logo
Thinkphp8 Vue3 Element PLus TypeScript Vite Pinia

🔥BuildAdmin是一个永久免费开源,无需授权即可商业使用,且使用了流行技术栈快速创建商业级后台管理系统。

热门标签

PHP ThinkPHP ThinkPHP5.1 Go Mysql Mysql5.7 Redis Linux CentOS7 Git HTML CSS CSS3 Javascript JQuery Vue LayUI VMware Uniapp 微信小程序 docker wiki Confluence7 学习笔记 uView ES6 Ant Design Pro of Vue React ThinkPHP6.0 chrome 扩展 翻译工具 Nuxt SSR 服务端渲染 scrollreveal.js ThinkPHP8.0 Mac webman 跨域CORS vscode GitHub ECharts Canvas vue3 three.js 微信支付 PHP全栈开发 Python AI 人工智能 AI生成 工作经验 实战笔记