Scope (2)

白鹤亮翅

在嵌套函数闭包中,内层函数自然能够向上读取外层作用域声明的变量:

def run():
    flag = False
    def print_flag():
        print(flag)
    print_flag() # False

run()

当场打脸

既然读取没问题,那我们在内层函数里顺手更新一下这个变量:

def run():
    flag = False
    def print_flag():
        print(flag)
    def set_to_true():
        flag = True
    print(flag)
    set_to_true()
    print(flag)
    return flag

run()

运行输出却让人摸不着头脑:

$ python3 test.py
False
False

明明调用了 set_to_true(),外层的 flag 却纹丝未动。

见招拆招

在读取变量时,Python 按照 LEGB 规则(Local -> Enclosing -> Global -> Built-in)逐层向上查找,所以 print_flag() 能够顺利找到 run() 作用域里的 flag。

但赋值操作的逻辑完全不同:如果在当前作用域中给一个变量赋值,Python 会默认在当前局部作用域创建一个新的局部变量。所以在 set_to_true() 中,flag = True 只是创建了一个局部的 flag 并遮蔽(shadow)了外层变量,函数返回后即被销毁,丝毫没有影响外层的 flag。

在 Python 3 中,可以使用 nonlocal 关键字明确声明该变量来自外层闭包:

def set_to_true():
    nonlocal flag
    flag = True

再次执行即可符合预期:

$ python3 test.py
False
True

避坑防身:在工程实践中频繁使用 nonlocal 或 global 容易导致闭包状态隐晦难以维护,通常建议通过显式返回值、类属性或字典容器来传递和更新状态。