Python 现代开发学习笔记

(六)闭包保存状态与 nonlocal

1. 返回一个记住外层名称的函数

希望分别建立“30 分钟达标”和“45 分钟达标”两套判断,可以让函数生成函数:

# 每次调用 make_checker,都建立自己的 goal 绑定和内部函数。
def make_checker(goal):
    def check(minutes):
        return minutes >= goal
    return check


daily = make_checker(30)
weekend = make_checker(45)
print(daily(35), weekend(35))

输出 True False。make_checker(30) 返回后,daily 指向内部的 check,它仍能读取那次外层调用的 goal。函数连同保留的外层绑定形成闭包。保留的是需要继续访问的绑定,不是把整个外层调用永久挂在调用栈上。

2. 用 nonlocal 更新外层函数的绑定

# add 的 total 来自外层 make_counter;每个计数器保留自己的状态。
def make_counter():
    total = 0

    def add(minutes):
        nonlocal total
        total += minutes
        return total

    return add


first = make_counter()
second = make_counter()
print(first(20), first(35), second(10))

输出 20 55 10。nonlocal total 表示这次赋值更新最近一层已经存在的外层函数绑定。它与 global 指向的位置不同:global 选模块层,nonlocal 选外层函数层。

两个闭包各自保留计数状态

图中第一份状态从 0 → 20 → 55,第二份从 0 → 10。如果删除 nonlocal,total += minutes 会被当作先读取、再重新绑定局部 total,而这个局部值还没有建立。

3. 循环中的闭包与延迟读取

# 在外层函数里循环定义,让三个 lambda 捕获同一个 goal 绑定。
def make_checks():
    checks = []
    for goal in [30, 45, 60]:
        checks.append(lambda minutes: minutes >= goal)
    return checks


checks = make_checks()
print([check(40) for check in checks])

# 默认值在每次执行 lambda 定义时选定,分别保留当时的整数对象。
def make_fixed_checks():
    checks = []
    for goal in [30, 45, 60]:
        checks.append(lambda minutes, target=goal: minutes >= target)
    return checks


fixed = make_fixed_checks()
print([check(40) for check in fixed])

第一行结果是 [False, False, False];循环结束时,外层 goal 已经是 60,三个函数后来都读取它。第二行是 [True, False, False];三个默认值分别是 30、45、60。

循环结束后读取绑定与定义时选定默认值的对照

这里的默认值是整数,保留后不会被原地修改。若保存的是列表,仍然会共享那个列表对象。另一种写法是在循环里调用前面的 make_checker(goal),让每次外层调用产生独立绑定。嵌套作用域与名称

笔记正文与全部题解 · Ctrl / ⌘ K