Python
基础语法
01概念
02安装
03变量
04字符串
05数
06常量与注释
07列表
08元组
09if语句
10字典
11集合
12复合数据类型对比
13推导式
14用户输入
15while循环
16函数
17类
18面向对象编程
19文件操作
20异常处理
21日期和时间
22魔术方法
23内置函数
24线程
25并发&并行
26正则表达式
27迭代器
28装饰器
29生成器
30上下文管理器
31函数式编程
32闭包
33解包
34工具库
35连接关系型数据库
36虚拟环境
37异步编程
网络爬虫
01urllib库[了解]
02requests库
03数据交换格式
04解析库
05lxml
06Beautiful Soup
07Xpath语法
08动态网页的处理
-
+
首页
32闭包
在 Python 中,**闭包(Closure)** 是一种特殊的嵌套函数结构:当内部函数引用了外部函数的变量(非全局变量),且外部函数返回了该内部函数时,这个内部函数及其引用的外部变量就构成了闭包。 闭包的核心特性是:**内部函数保留了对外部函数局部变量的引用,即使外部函数已经执行完毕,这些变量也不会被销毁**,而是被内部函数 “记住” 并持续使用。 ## 构成条件 一个嵌套函数要成为闭包,必须满足三个条件: 1. **存在嵌套结构**:有一个外层函数(outer function)和一个内层函数(inner function),内层函数定义在外层函数内部。 2. **内部函数引用外部变量**:内层函数使用了外层函数中定义的变量(非全局变量)。 3. **外部函数返回内部函数**:外层函数的返回值是内层函数本身(而非函数调用结果)。 ## 示例 ```python def outer_function(message): # 外部函数的局部变量 outer_var = message # 被内部函数引用 # 内部函数(嵌套在外部函数中) def inner_function(): # 引用外部函数的变量outer_var print(outer_var) # 外部函数返回内部函数(注意:返回的是函数对象,不是调用结果) return inner_function # 调用外部函数,得到闭包(内部函数) closure = outer_function("Hello, Closure!") # 执行闭包(此时外部函数已执行完毕,但内部函数仍能访问outer_var) closure() # 输出:Hello, Closure! ``` 分析: - 当outer_function("Hello, Closure!")执行完毕后,其局部变量outer_var本应被销毁(函数调用结束后局部变量通常会被回收)。 - 但由于inner_function引用了outer_var,且outer_function返回了inner_function,Python 会将outer_var“保留” 下来,供inner_function后续调用时使用。 - 这种 “保留外部变量并持续访问” 的特性,就是闭包的核心。 ## 闭包如何 “保存状态” 闭包的核心价值之一是**保存外部函数的状态**(即外部变量的值)。每次调用外部函数生成闭包时,会创建独立的变量副本,不同闭包之间的状态互不干扰。 例如:用闭包实现计数器。 ```python def make_counter(): count = 0 # 外部函数的局部变量(被闭包保存的状态) def counter(): nonlocal count # 声明count是外部函数的变量(允许修改) count += 1 return count return counter # 创建两个独立的计数器(闭包) counter1 = make_counter() counter2 = make_counter() # 调用counter1:状态独立 print(counter1()) # 1 print(counter1()) # 2 print(counter1()) # 3 # 调用counter2:状态与counter1无关 print(counter2()) # 1 print(counter2()) # 2 ``` 分析: - make_counter每次被调用时,都会创建一个新的count变量(初始值 0)和一个新的counter函数。 - counter1和counter2是两个独立的闭包,各自保存自己的count状态,因此调用结果互不影响。 - nonlocal count用于声明count是外部函数的变量(而非局部变量),允许内部函数修改其值(若不声明,直接修改会报错)。 ## 底层原理 Python 中,闭包通过\_\_closure\_\_属性保存对外部变量的引用。\_\_closure\_\_是一个元组,其中的每个元素是一个cell对象,包含被引用的外部变量的值。 例如:查看闭包引用的变量。 ```python def outer(x): def inner(): return x # 引用外部变量x return inner closure = outer(10) # 查看闭包的__closure__属性 print(closure.__closure__) # 输出:(<cell at 0x...: int object at 0x...>,) # 从cell对象中获取变量值 print(closure.__closure__[0].cell_contents) # 输出:10 # 调用闭包,验证是否使用保存的变量 print(closure()) # 输出:10 ``` **结论**:闭包并非 “复制” 了外部变量,而是通过\_\_closure\_\_保留了对变量的**引用**,因此即使外部函数执行完毕,变量仍能被访问。 ## 应用场景 闭包在 Python 开发中应用广泛,尤其适合需要 “**封装状态 + 延迟执行**” 的场景: **1. 实现装饰器(Decorator)** 装饰器是闭包最典型的应用。装饰器本质是一个接收函数作为参数,并返回新函数的闭包,用于在不修改原函数代码的情况下扩展其功能。 用闭包实现日志装饰器: ```python def log_decorator(func): # 外部函数接收被装饰的函数作为参数 def wrapper(*args, **kwargs): # 内部函数(闭包)扩展原函数功能 print(f"调用函数:{func.__name__},参数:{args}, {kwargs}") result = func(*args, **kwargs) # 调用原函数 print(f"函数{func.__name__}返回:{result}") return result return wrapper # 返回闭包 # 使用装饰器 @log_decorator # 等价于:add = log_decorator(add) def add(a, b): return a + b add(2, 3) # 输出: # 调用函数:add,参数:(2, 3), {} # 函数add返回:5 ``` **2. 数据封装与私有变量** 闭包可模拟 “私有变量”:外部函数的变量只能通过内部函数访问或修改,无法直接从外部修改,实现数据封装。 用闭包封装用户信息: ```python def make_user(name, age): # 外部变量(模拟私有变量,无法直接访问) _name = name _age = age def get_info(): # 访问私有变量 return f"姓名:{_name},年龄:{_age}" def set_age(new_age): nonlocal _age # 限制年龄范围(封装逻辑) if new_age > 0 and new_age < 150: _age = new_age else: raise ValueError("年龄必须在0-150之间") # 返回操作私有变量的接口 return get_info, set_age # 创建用户(获取闭包接口) get_info, set_age = make_user("Alice", 25) # 通过接口访问/修改数据 print(get_info()) # 姓名:Alice,年龄:25 set_age(26) print(get_info()) # 姓名:Alice,年龄:26 # 无法直接修改_name/_age(外部无访问路径) # _name = "Bob" # 报错:NameError: name '_name' is not defined ``` **3. 延迟计算(Lazy Evaluation)** 闭包可延迟计算逻辑的执行:先定义计算所需的参数和逻辑,在需要时再调用闭包执行计算。 延迟计算两数之和: ```python def lazy_add(a, b): # 先保存参数,不立即计算 def calculate(): return a + b # 延迟到调用时计算 return calculate # 定义计算逻辑(此时不执行加法) add_later = lazy_add(3, 5) # 其他操作... print("准备计算...") # 需要时执行计算 print(add_later()) # 输出:8 ``` **4. 回调函数与事件处理** 在异步编程或事件驱动场景中,闭包可作为回调函数,保存事件触发时所需的上下文信息(如参数、状态)。 为按钮点击事件绑定带参数的回调: ```python def make_click_handler(message): # 保存回调所需的消息(上下文) def on_click(): print(f"按钮被点击:{message}") return on_click # 为两个按钮绑定不同的回调(带不同消息) button1_handler = make_click_handler("提交表单") button2_handler = make_click_handler("取消操作") # 模拟按钮点击 button1_handler() # 输出:按钮被点击:提交表单 button2_handler() # 输出:按钮被点击:取消操作 ``` ## 注意事项 **变量引用而非复制**:闭包引用的是外部变量的**地址**,而非值的副本。若外部变量在闭包创建后被修改,闭包会使用修改后的值(除非用默认参数固定值)。 例如: ```python def outer(): x = 10 def inner(): print(x) x = 20 # 外部函数中修改x return inner closure = outer() closure() # 输出:20(闭包引用的是最终修改后的值) ``` 解决方法:用默认参数固定值(创建闭包时复制值) ```python def outer(): x = 10 def inner(x=x): # 用默认参数固定x的值 print(x) x = 20 return inner closure = outer() closure() # 输出:10(固定为创建时的值) ``` **内存占用问题**:闭包会保留外部变量的引用,导致这些变量不会被垃圾回收,长期使用可能增加内存消耗(尤其在循环中创建大量闭包时)。 **nonlocal与global的区别**: - nonlocal用于修改**外部函数的局部变量**(非全局); - global用于修改**全局变量**。若内部函数需修改外部变量,需根据变量作用域正确声明。 ## 总结 闭包是 Python 中一种强大的特性,其核心是 “**内部函数保留对外部变量的引用**”,主要特点和价值如下: - **结构**:由嵌套函数构成,满足 “内部引用外部变量 + 外部返回内部函数”。 - **核心能力**:保存外部函数的状态,实现数据封装和延迟执行。 - **典型应用**:装饰器、计数器、私有变量模拟、回调函数等。 理解闭包不仅能帮助你写出更优雅的代码(如简洁的装饰器),还能加深对 Python 变量作用域和函数本质的理解。
毛林
2025年9月7日 11:45
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
Markdown文件
PDF文档(打印)
分享
链接
类型
密码
更新密码