目录

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)  # 5

2.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 = 26

3. 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())    # 0

4. 闭包的实际应用

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 和 _age

4.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))    # 16

4.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 16

5.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 16

6. 闭包 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 类:闭包更轻量,适合简单的状态封装

练习

  1. 使用闭包创建一个累加器,可以设置初始值,支持加法和减法
  2. 实现一个记忆化(Memoization)闭包,缓存函数调用结果
  3. 使用闭包实现一个简单的日志记录器,可以设置日志级别
  4. 创建一个闭包工厂,生成不同折扣率的打折函数