深入理解Python闭包:不仅仅是函数嵌套

发布时间:2026/5/20 13:48:11

深入理解Python闭包:不仅仅是函数嵌套 当你写一个函数时通常认为它运行完就结束了所有局部变量都会消失。但有没有想过能否让函数的“记忆”延续下去记住上一次运行时的状态这就是闭包要解决的问题。一、为什么需要闭包——从计数器说起假设我们需要一个计数器每次调用就加1。你可能会想到用全局变量count 0 # 全局变量 def counter(): global count count 1 return count print(counter()) # 1 print(counter()) # 2 print(counter()) # 3这种方法有三个明显问题污染全局命名空间count变量暴露在全局不安全任何地方都可以修改count无法创建多个独立计数器你可能会想那就用类吧class Counter: def __init__(self): self.count 0 def increment(self): self.count 1 return self.count c1 Counter() print(c1.increment()) # 1 print(c1.increment()) # 2 c2 Counter() print(c2.increment()) # 1类确实解决了问题但有没有更简单、更轻量的方法这就是闭包的用武之地。二、闭包的核心概念1. 闭包的定义闭包是一个函数它定义在另一个函数内部嵌套函数引用了外部函数的变量即使外部函数执行完毕内部函数仍然能记住并访问这些变量用代码表示就是def outer_func(): # 外部函数 message Hello # 外部函数的局部变量 def inner_func(): # 内部函数闭包 print(message) # 引用了外部函数的变量 return inner_func # 返回内部函数不调用 # 使用 my_func outer_func() # outer_func执行完毕 my_func() # 输出Hello神奇之处outer_func()执行完后按理说它的局部变量message应该被销毁。但inner_func仍然能访问它2. 闭包三要素理解闭包需要掌握三个关键点def make_multiplier(factor): # 外部函数 # factor是外部函数的局部变量 def multiplier(number): # 内部函数闭包 # multiplier引用了外部函数的factor return number * factor return multiplier # 返回内部函数 # 创建两个不同的乘法器 double make_multiplier(2) # factor2被记住了 triple make_multiplier(3) # factor3被记住了 print(double(5)) # 10 (5 * 2) print(triple(5)) # 15 (5 * 3)这里体现了闭包的三要素函数嵌套multiplier定义在make_multiplier内部引用外部变量multiplier引用了factor返回内部函数make_multiplier返回multiplier三、闭包如何工作——深入原理1. 作用域链的魔力要理解闭包先要明白Python的作用域规则x global # 全局作用域 def outer(): x outer # 闭包作用域 def inner(): x inner # 局部作用域 print(x) # 输出inner先找局部 inner() print(x) # 输出outer outer() print(x) # 输出global当函数执行时Python会按照LEGB规则查找变量Local局部→ 当前函数内部Enclosing闭包→ 外层函数Global全局→ 模块级别Built-in内置→ Python内置函数闭包的关键在于内部函数保持了对外部函数作用域的引用即使外部函数已经执行完毕。2. 闭包的实际存储位置闭包引用的外部变量存储在哪里让我们看看def outer(): x 10 y 20 def inner(): # 只引用x不引用y return x 5 return inner func outer() print(func()) # 15 # 查看闭包保存的变量 print(func.__closure__) # 有内容说明是闭包 print(func.__closure__[0].cell_contents) # 10重要发现闭包只保存它用到的外部变量而不是所有外部变量。上面的例子中y没有被保存。四、闭包的实际应用1. 实现有状态的函数闭包最常见的用途是创建有状态的函数def make_counter(): count 0 def counter(): nonlocal count # 声明count不是局部变量而是闭包变量 count 1 return count return counter # 创建两个独立的计数器 counter1 make_counter() counter2 make_counter() print(counter1()) # 1 print(counter1()) # 2 print(counter1()) # 3 print(counter2()) # 1独立的计数器 print(counter2()) # 2注意这里使用了nonlocal关键字。在Python 3中如果要在闭包中修改外部变量必须使用nonlocal声明。2. 配置化函数工厂闭包可以用来创建预配置的函数def make_greeter(greeting): 创建不同问候方式的函数 def greeter(name): return f{greeting}, {name}! return greeter # 创建不同的问候器 say_hello make_greeter(Hello) say_hi make_greeter(Hi) say_hey make_greeter(Hey) print(say_hello(Alice)) # Hello, Alice! print(say_hi(Bob)) # Hi, Bob! print(say_hey(Charlie)) # Hey, Charlie!这种方式在需要创建多个功能相似但配置不同的函数时非常有用。3. 数据隐藏和封装闭包可以实现类似面向对象的封装def create_bank_account(initial_balance0): balance initial_balance def deposit(amount): nonlocal balance if amount 0: balance amount return f存款成功余额{balance} return 存款金额必须大于0 def withdraw(amount): nonlocal balance if 0 amount balance: balance - amount return f取款成功余额{balance} return 余额不足或金额无效 def get_balance(): return balance # 返回一个字典包含操作函数 return { deposit: deposit, withdraw: withdraw, get_balance: get_balance } # 创建银行账户 account create_bank_account(1000) print(account[deposit](500)) # 存款成功余额1500 print(account[withdraw](200)) # 取款成功余额1300 print(account[get_balance]()) # 1300 # 无法直接访问balance变量 # print(balance) # 报错NameError优点balance是私有的外部无法直接访问只能通过提供的函数操作数据实现了信息隐藏五、闭包的陷阱与解决方案陷阱1延迟绑定问题这是闭包最常见的问题def create_functions(): functions [] for i in range(3): def func(): return i functions.append(func) return functions funcs create_functions() for f in funcs: print(f()) # 输出2, 2, 2为什么不是0, 1, 2因为闭包记住的是变量i而不是i的值。循环结束时i2所有函数都引用同一个i。解决方案1使用默认参数def create_functions(): functions [] for i in range(3): def func(ii): # 使用默认参数捕获当前值 return i functions.append(func) return functions funcs create_functions() for f in funcs: print(f()) # 输出0, 1, 2解决方案2使用工厂函数def make_func(i): def func(): return i return func def create_functions(): functions [] for i in range(3): functions.append(make_func(i)) return functions funcs create_functions() for f in funcs: print(f()) # 输出0, 1, 2陷阱2内存泄漏闭包会长期持有外部变量的引用可能导致内存无法释放def create_big_closure(): big_data [i for i in range(1000000)] # 大数据 def inner(): return len(big_data) return inner func create_big_closure() # big_data一直被func引用无法被垃圾回收解决方案及时清理不再需要的闭包func create_big_closure() # 使用完后... func None # 释放对闭包的引用big_data可以被回收六、闭包 vs 类闭包和类都能实现状态保存如何选择使用闭包的情况# 闭包实现计数器 def make_counter(): count 0 def counter(): nonlocal count count 1 return count return counter c make_counter()优点更简洁代码量少创建速度快内存占用小适合简单状态使用类的情况# 类实现计数器 class Counter: def __init__(self): self.count 0 def __call__(self): self.count 1 return self.count c Counter()优点功能更强大有多个方法支持继承和多态适合复杂状态和行为选择建议如果只需要一个函数和少量状态 → 用闭包如果需要多个方法或复杂功能 → 用类如果要有继承、多态等特性 → 用类七、闭包在装饰器中的应用装饰器是闭包最经典的应用def logger(func): 记录函数调用的装饰器 def wrapper(*args, **kwargs): print(f调用函数{func.__name__}) result func(*args, **kwargs) print(f函数 {func.__name__} 执行完毕) return result return wrapper logger def add(a, b): return a b print(add(3, 5)) # 输出 # 调用函数add # 函数 add 执行完毕 # 8这里wrapper是一个闭包它记住了外部函数的func参数。八、高级闭包技巧1. 闭包实现函数缓存记忆化def make_cached(func): 为函数添加缓存功能的装饰器 cache {} def cached_func(*args): if args in cache: print(f从缓存获取结果{args}) return cache[args] result func(*args) cache[args] result return result return cached_func make_cached def expensive_computation(x, y): print(f计算 {x} {y}...) return x y print(expensive_computation(3, 4)) # 计算 3 4... print(expensive_computation(3, 4)) # 从缓存获取结果(3, 4) print(expensive_computation(5, 6)) # 计算 5 6...2. 闭包实现简单状态机def create_state_machine(): state IDLE def transition(event): nonlocal state if state IDLE and event start: state RUNNING return 开始运行 elif state RUNNING and event stop: state STOPPED return 停止运行 elif state STOPPED and event reset: state IDLE return 重置 else: return f无效操作当前状态{state}事件{event} def get_state(): return state return transition, get_state transition, get_state create_state_machine() print(transition(start)) # 开始运行 print(get_state()) # RUNNING print(transition(stop)) # 停止运行 print(get_state()) # STOPPED print(transition(reset)) # 重置 print(get_state()) # IDLE九、练习实现一个简单的购物车用闭包实现一个购物车系统def create_shopping_cart(): items [] def add_item(item, price): items.append((item, price)) return f添加{item}价格{price} def remove_item(item): for i, (name, price) in enumerate(items): if name item: items.pop(i) return f移除{item} return f未找到商品{item} def get_total(): total sum(price for _, price in items) return total def get_items(): return items.copy() # 返回副本保护内部数据 return { add: add_item, remove: remove_item, total: get_total, items: get_items } # 使用购物车 cart create_shopping_cart() print(cart[add](苹果, 5)) print(cart[add](香蕉, 3)) print(cart[add](橙子, 4)) print(f购物车内容{cart[items]()}) print(f总价{cart[total]()}) print(cart[remove](香蕉)) print(f移除后总价{cart[total]()})十、总结闭包是Python中一个强大而优雅的特性让我们总结一下关键点1. 闭包的核心函数嵌套函数内部函数引用外部函数变量外部函数返回内部函数2. 闭包的用途创建有状态的函数实现数据隐藏和封装创建配置化函数工厂实现装饰器模式3. 使用注意事项注意延迟绑定问题避免意外内存泄漏使用nonlocal修改外部变量4. 闭包 vs 类闭包轻量级适合简单状态类功能完整适合复杂场景5. 最佳实践明确闭包要捕获的变量及时释放不再需要的闭包合理使用闭包避免过度复杂闭包的哲学函数不仅仅是执行代码的机器它可以携带记忆记住创建时的环境。这种能力让函数变得更加灵活和强大。一个生动的比喻普通函数 → 计算器用完就忘闭包 → 带有记忆功能的计算器记得之前的计算类 → 多功能计算器有多种功能和状态掌握了闭包你就拥有了编写更加优雅、高效的Python代码的能力。试着在你下一个项目中用闭包解决实际问题你会发现它的强大之处

相关新闻