一、闭包基础概念

1.1 什么是闭包 —— 三要素

脚本:01_closure_basic.py

def outer(x):
    def inner(y):
        return x + y          # ① 引用外部变量 x
    return inner              # ② 返回内函数

add_5 = outer(5)              # ③ 外函数已结束,但 x 被保留
print(add_5(3))               # 8
print(add_5.__closure__)      # 查看 cell 对象

知识点:内函数引用外函数局部变量 + 外函数返回内函数 + 外函数已结束但变量存活。


二、闭包 vs 普通函数 / 全局变量

2.1 对比全局变量 —— 避免污染

脚本:02_vs_global.py

# 全局变量方案
counter = 0
def inc_global():
    global counter; counter += 1; return counter

# 闭包方案(无全局)
def make_counter():
    count = 0
    def inc():
        nonlocal count; count += 1; return count
    return inc

c = make_counter()
for i in range(3):
    print("global:", inc_global(), "closure:", c())

结论:闭包封装状态,不污染全局命名空间。


三、nonlocal 与变量修改

3.1 只读陷阱 —— 未声明 nonlocal

脚本:03_readonly_trap.py

def make_adder(x):
    def adder(y):
        # x += y  # UnboundLocalError
        return x + y
    return adder

add5 = make_adder(5)
print(add5(3))  # 8

3.2 nonlocal 修改外部变量

脚本:04_nonlocal.py

def make_adder(x):
    def adder(y):
        nonlocal x
        x += y
        return x
    return adder

add5 = make_adder(5)
print(add5(3))  # 8
print(add5(2))  # 10  状态被保留

四、参数与默认值陷阱

4.1 默认值列表陷阱

脚本:05_default_list_trap.py

# 错误:共享同一份列表
def append(x, lst=[]):  
    lst.append(x); return lst

print(append(1))  # [1]
print(append(2))  # [1, 2]  意外共享!

# 闭包方案:每次新列表
def make_appender():
    lst = []
    def append(x):
        lst.append(x); return lst
    return append

app = make_appender()
print(app(1))  # [1]
print(app(2))  # [1, 2]  正常

五、多层嵌套闭包

5.1 三层嵌套

脚本:`06_nested_closure.py"

def f1(a):
    def f2(b):
        def f3(c):
            return a + b + c
        return f3
    return f2

add = f1(10)(5)   # a=10, b=5
print(add(3))     # 18  (a+b+c)

六、闭包 + 装饰器

6.1 手写计时装饰器(闭包保存原函数)

脚本:`07_timer_decorator.py"

import time, functools

def timer(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        elapsed = time.perf_counter() - start
        print(f"{func.__name__} took {elapsed:.4f}s")
        return result
    return wrapper

@timer
def slow_add(a, b):
    time.sleep(0.5); return a + b

print(slow_add(3, 4))

知识点wrapper 是闭包,保存 func 引用。


七、闭包 vs 类(状态封装)

7.1 用闭包实现计数器

脚本:`08_closure_counter.py"

def make_counter(start=0):
    count = start
    def inc(step=1):
        nonlocal count
        count += step
        return count
    def dec(step=1):
        nonlocal count
        count -= step
        return count
    return inc, dec

inc, dec = make_counter(10)
print(inc())   # 11
print(dec())   # 10

7.2 同等功能用类实现

脚本:`09_class_counter.py"

class Counter:
    def __init__(self, start=0):
        self.count = start
    def inc(self, step=1):
        self.count += step
        return self.count
    def dec(self, step=1):
        self.count -= step
        return self.count

c = Counter(10)
print(c.inc())  # 11
print(c.dec())  # 10

对比:闭包轻量、无类实例开销;类更易扩展、继承。


八、性能与陷阱

8.1 闭包 vs 类访问速度

脚本:`10_speed_closure_vs_class.py"

import timeit

def make_adder(x):
    def add(y):
        return x + y
    return add

class Adder:
    def __init__(self, x):
        self.x = x
    def __call__(self, y):
        return self.x + y

add_clos = make_adder(5)
add_inst = Adder(5)

t1 = timeit.timeit('add_clos(3)', globals=globals(), number=10000000)
t2 = timeit.timeit('add_inst(3)', globals=globals(), number=10000000)
print("closure:", t1, "class:", t2)  # 闭包略快(无 self 解析)

8.2 循环变量陷阱

脚本:`11_loop_closure_trap.py"

# 错误:全部绑定到同一个 i
funcs = [lambda x: x + i for i in range(3)]
print([f(10) for f in funcs])  # [12, 12, 12]  i=2

# 正确:默认参数捕获当前 i
funcs_fix = [lambda x, i=i: x + i for i in range(3)]
print([f(10) for f in func_fix])  # [10, 11, 12]

九、综合案例

9.1 可重用计时器(带重置)

脚本:`12_timer_with_reset.py"

def make_timer():
    import time
    start = time.perf_counter()
    def elapsed():
        return time.perf_counter() - start
    def reset():
        nonlocal start
        start = time.perf_counter()
    return elapsed, reset

elapsed, reset = make_timer()
time.sleep(0.1)
print("elapsed:", elapsed())
reset(); time.sleep(0.05)
print("after reset:", elapsed())

9.2 记忆化(缓存)闭包

脚本:`13_memoize_closure.py"

def memoize(func):
    cache = {}
    def wrapper(*args):
        if args in cache:
            return cache[args]
        result = func(*args)
        cache[args] = result
        return result
    return wrapper

@memoize
def fib(n):
    return n if n < 2 else fib(n-1) + fib(n-2)

print(fib(50))  # 秒出

9.3 配置封装(嵌套 dict + 闭包)

脚本:`14_config_closure.py"

def make_config(defaults):
    cfg = dict(defaults)
    def get(key):
        return cfg[key]
    def set(key, value):
        cfg[key] = value
    def all():
        return cfg.copy()
    return get, set, all

get, set, all = make_config({"host": "localhost", "port": 8000})
set("port", 9000)
print("config:", all())
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐