Python 闭包
目录
学习目标
- 理解闭包的概念和工作原理
- 掌握嵌套函数和 nonlocal 关键字
- 学会使用闭包实现数据隐藏和工厂函数
- 理解闭包与装饰器的关系
1. 嵌套函数
在 Python 中,函数内部可以定义另一个函数,这就是嵌套函数。
def outer():
"""外部函数"""
print("外部函数执行")
def inner():
"""内部函数"""
print("内部函数执行")
inner()
print("外部函数结束")
outer()输出:
外部函数执行
内部函数执行
外部函数结束1.1 内部函数访问外部变量
def greeting(name):
"""问候函数"""
message = f"你好, {name}"
def say_hello():
"""内部函数访问外部变量"""
print(message) # 访问外部函数的变量
say_hello()
greeting("小明")
# 输出: 你好, 小明2. 什么是闭包
**闭包(Closure)**是指一个函数记住了它的词法作用域,即使这个函数在其词法作用域之外执行,也能访问那个作用域中的变量。
2.1 最简单的闭包
def make_multiplier(n):
"""创建乘法器"""
def multiplier(x):
"""闭包:记住 n 的值"""
return x * n
return multiplier # 返回内部函数
# 创建两个不同的乘法器
times3 = make_multiplier(3)
times5 = make_multiplier(5)
print(times3(10)) # 30
print(times5(10)) # 50
# 验证它们是不同的闭包
print(times3.__closure__[0].cell_contents) # 3
print(times5.__closure__[0].cell_contents) # 52.2 闭包的三要素
def demonstrate_closure():
"""演示闭包的三要素"""
# 1. 外部函数
def outer_function(x):
# 2. 外部变量(被内部函数引用)
count = 0
# 3. 内部函数(返回但不立即执行)
def inner_function(y):
nonlocal count # 声明使用外部变量
count += 1
return x + y + count
return inner_function
return outer_function
# 创建闭包
closure_maker = demonstrate_closure()
closure1 = closure_maker(10)
closure2 = closure_maker(20)
# 测试
print(closure1(5)) # 10 + 5 + 1 = 16
print(closure1(5)) # 10 + 5 + 2 = 17
print(closure2(5)) # 20 + 5 + 1 = 263. nonlocal 关键字
nonlocal 用于在内部函数中修改外部(非全局)函数的变量。
3.1 不使用 nonlocal
def counter_without_nonlocal():
"""不使用 nonlocal - 无法修改外部变量"""
count = 0
def increment():
count = count + 1 # 错误!会创建局部变量
return count
return increment
# 这会报错:UnboundLocalError
# counter = counter_without_nonlocal()
# counter()3.2 使用 nonlocal
def make_counter():
"""使用 nonlocal 创建计数器"""
count = 0
def increment():
nonlocal count # 声明使用外部变量
count += 1
return count
def decrement():
nonlocal count
count -= 1
return count
def get_count():
return count
def reset():
nonlocal count
count = 0
# 返回多个闭包函数
return increment, decrement, get_count, reset
# 创建计数器
inc, dec, get, reset = make_counter()
print(inc()) # 1
print(inc()) # 2
print(inc()) # 3
print(dec()) # 2
print(get()) # 2
reset()
print(get()) # 04. 闭包的实际应用
4.1 数据隐藏(私有变量)
def create_person(name, age):
"""创建人员对象(使用闭包实现数据隐藏)"""
_name = name # "私有"变量
_age = age
def get_name():
return _name
def get_age():
return _age
def set_age(new_age):
nonlocal _age
if new_age < 0:
raise ValueError("年龄不能为负数")
_age = new_age
def info():
return f"{_name}, {_age}岁"
return {
'get_name': get_name,
'get_age': get_age,
'set_age': set_age,
'info': info
}
# 使用
person = create_person("小明", 25)
print(person['info']()) # 小明, 25岁
person['set_age'](26)
print(person['info']()) # 小明, 26岁
# 无法直接访问 _name 和 _age4.2 工厂函数
def power_factory(exponent):
"""幂函数工厂"""
def power(base):
return base ** exponent
return power
# 创建不同的幂函数
square = power_factory(2) # 平方
cube = power_factory(3) # 立方
quartic = power_factory(4) # 四次方
print(square(5)) # 25
print(cube(3)) # 27
print(quartic(2)) # 164.3 配置函数
def make_formatter(prefix="", suffix=""):
"""创建文本格式化器"""
def formatter(text):
return f"{prefix}{text}{suffix}"
return formatter
# 创建不同的格式化器
bold = make_formatter("**", "**")
italic = make_formatter("*", "*")
code = make_formatter("`", "`")
quote = make_formatter("> ", "")
print(bold("重要")) # **重要**
print(italic("强调")) # *强调*
print(code("print()")) # `print()`
print(quote("引用")) # > 引用5. 闭包与延迟绑定
5.1 经典陷阱
def create_functions():
"""创建函数列表(有问题的实现)"""
functions = []
for i in range(5):
def func():
return i * i # i 是延迟绑定的
functions.append(func)
return functions
# 测试
funcs = create_functions()
for f in funcs:
print(f(), end=" ") # 16 16 16 16 16(都是 4*4)5.2 解决方案
def create_functions_fixed():
"""修复延迟绑定问题"""
functions = []
for i in range(5):
def make_func(x):
def func():
return x * x # x 是在定义时绑定的
return func
functions.append(make_func(i))
return functions
# 测试
funcs = create_functions_fixed()
for f in funcs:
print(f(), end=" ") # 0 1 4 9 165.3 使用默认参数
def create_functions_default():
"""使用默认参数修复"""
functions = []
for i in range(5):
def func(x=i): # 默认参数在定义时求值
return x * x
functions.append(func)
return functions
# 测试
funcs = create_functions_default()
for f in funcs:
print(f(), end=" ") # 0 1 4 9 166. 闭包 vs 类
闭包和类都可以用来封装数据,各有优劣:
# 使用类实现计数器
class CounterClass:
def __init__(self):
self.count = 0
def increment(self):
self.count += 1
return self.count
# 使用闭包实现计数器
def make_counter_closure():
count = 0
def increment():
nonlocal count
count += 1
return count
return increment
# 对比
print("=== 类实现 ===")
counter1 = CounterClass()
print(counter1.increment()) # 1
print(counter1.increment()) # 2
print("=== 闭包实现 ===")
counter2 = make_counter_closure()
print(counter2()) # 1
print(counter2()) # 2
# 闭包更轻量
import sys
print(f"类实例大小: {sys.getsizeof(CounterClass())} 字节")
print(f"闭包大小: {sys.getsizeof(make_counter_closure())} 字节")7. 检查闭包
def make_closure(x, y):
z = 100
def closure():
return x + y + z
return closure
cl = make_closure(10, 20)
# 检查闭包
print(f"是否是闭包: {cl.__closure__ is not None}")
print(f"闭包变量数量: {len(cl.__closure__)}")
# 查看闭包中的变量值
for i, cell in enumerate(cl.__closure__):
print(f"变量 {i}: {cell.cell_contents}")
# 查看自由变量名
print(f"自由变量: {cl.__code__.co_freevars}")本节小结
- 闭包:函数记住了定义时的环境,即使在外部执行也能访问外部变量
- 三要素:外部函数、被引用的外部变量、返回的内部函数
- nonlocal:在内部函数中修改外部函数的变量
- 应用:数据隐藏、工厂函数、配置函数
- 延迟绑定陷阱:循环中创建的闭包共享同一个变量引用
- 闭包 vs 类:闭包更轻量,适合简单的状态封装
练习
- 使用闭包创建一个累加器,可以设置初始值,支持加法和减法
- 实现一个记忆化(Memoization)闭包,缓存函数调用结果
- 使用闭包实现一个简单的日志记录器,可以设置日志级别
- 创建一个闭包工厂,生成不同折扣率的打折函数