函数进阶:作用域与闭包详解

4 阅读3分钟

一、Python 作用域规则:LEGB 原则

Python 查找变量时遵循 LEGB 规则,按以下顺序搜索:

  1. Local(局部作用域)- 当前函数内部
  2. Enclosing(嵌套作用域)- 外层嵌套函数
  3. Global(全局作用域)- 当前模块级别
  4. Built-in(内置作用域)- Python 内置命名空间

代码示例:LEGB 规则演示

# 全局变量
x = "global"

def outer():
    # 嵌套作用域
    x = "enclosing"
    
    def inner():
        # 局部作用域
        x = "local"
        print(f"inner: {x}")
    
    inner()
    print(f"outer: {x}")

outer()
print(f"global: {x}")

# 输出:
# inner: local
# outer: enclosing
# global: global

二、global 与 nonlocal 关键字

2.1 global 关键字

用于在函数内部修改全局变量:

count = 0

def increment():
    global count
    count += 1
    print(f"Count: {count}")

increment()  # Count: 1
increment()  # Count: 2
print(f"Final: {count}")  # Final: 2

2.2 nonlocal 关键字

用于在嵌套函数中修改外层函数的变量:

def make_counter():
    count = 0
    
    def increment():
        nonlocal count
        count += 1
        return count
    
    return increment

counter = make_counter()
print(counter())  # 1
print(counter())  # 2
print(counter())  # 3

三、闭包(Closure)详解

闭包是指一个函数对象,它可以访问并"记住"其定义时的外部作用域中的变量,即使外部函数已经执行完毕。

闭包的三个条件:

  1. 必须有嵌套函数
  2. 内层函数引用外层函数的变量
  3. 外层函数返回内层函数

3.1 基础闭包示例

def make_multiplier(factor):
    """创建一个乘法器闭包"""
    
    def multiply(number):
        return number * factor
    
    return multiply

# 创建不同的乘法器
double = make_multiplier(2)
triple = make_multiplier(3)

print(double(5))   # 10
print(triple(5))   # 15
print(double(10))  # 20

3.2 闭包的实际应用:装饰器基础

def timer_decorator(func):
    """简单的计时装饰器"""
    import time
    
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"{func.__name__} 执行时间:{end - start:.4f}秒")
        return result
    
    return wrapper

@timer_decorator
def slow_function():
    total = 0
    for i in range(1000000):
        total += i
    return total

result = slow_function()
print(f"结果:{result}")

3.3 闭包陷阱:循环变量绑定

# ❌ 错误示例
functions = []
for i in range(3):
    functions.append(lambda: i)

print([f() for f in functions])  # [2, 2, 2] - 都是 2!

# ✅ 正确示例:使用默认参数绑定
functions = []
for i in range(3):
    functions.append(lambda x=i: x)

print([f() for f in functions])  # [0, 1, 2] - 正确!

# ✅ 或使用闭包
functions = []
for i in range(3):
    def make_func(val):
        return lambda: val
    functions.append(make_func(i))

print([f() for f in functions])  # [0, 1, 2] - 正确!

四、实战案例:用闭包实现状态保持

4.1 简易缓存系统

def create_cache():
    """创建一个带缓存功能的函数"""
    cache = {}
    
    def get_or_compute(key, compute_func):
        if key not in cache:
            print(f"计算 {key}...")
            cache[key] = compute_func()
        else:
            print(f"从缓存获取 {key}")
        return cache[key]
    
    return get_or_compute

# 使用缓存
cache = create_cache()

def expensive_compute():
    return sum(range(1000000))

print(cache("sum", expensive_compute))  # 首次计算
print(cache("sum", expensive_compute))  # 从缓存获取

4.2 函数调用计数器

def count_calls(func):
    """统计函数调用次数的装饰器"""
    count = 0
    
    def wrapper(*args, **kwargs):
        nonlocal count
        count += 1
        print(f"{func.__name__} 被调用 {count} 次")
        return func(*args, **kwargs)
    
    wrapper.call_count = lambda: count
    return wrapper

@count_calls
def greet(name):
    return f"Hello, {name}!"

print(greet("Alice"))
print(greet("Bob"))
print(f"总调用次数:{greet.call_count()}")

五、closure 属性:查看闭包变量

Python 允许我们检查函数的闭包内容:

def outer(x):
    def inner(y):
        return x + y
    return inner

closure_func = outer(10)

# 查看闭包信息
print(f"闭包变量数:{len(closure_func.__closure__)}")
print(f"闭包值:{closure_func.__closure__[0].cell_contents}")
print(f"调用结果:{closure_func(5)}")  # 15

📝 总结

概念用途关键字
局部作用域函数内部变量-
全局作用域模块级变量global
嵌套作用域外层函数变量nonlocal
闭包保持状态、工厂函数-

核心要点:

  1. 牢记 LEGB 查找顺序
  2. 修改外部变量时使用 globalnonlocal
  3. 闭包可以"记住"创建时的环境
  4. 注意循环中闭包的变量绑定问题