为何Python闭包中f1的running_high值出现非预期变更?
核心问题出在闭包的变量绑定和nonlocal关键字的作用上,下面一步步拆解执行过程:
创建f0:
执行f0 = announce_highest(1)时,announce_highest函数会创建自己的局部变量环境:who=1、last_score=0、running_high=0,返回的内部函数f(即f0)会绑定这个环境,相当于"记住"了这三个变量。创建f1:
调用f0(12, 0)时,计算出玩家1的当前得分是0,得分差0-0=0不大于running_high=0,于是返回announce_highest(1, 0, 0)的新调用结果。这是一个全新的announce_highest实例,它有独立的局部变量环境:who=1、last_score=0、running_high=0,返回的内部函数就是f1,它绑定的是这个新环境。第一次调用f1(生成f2):
调用f1(12, 11)时,玩家1的得分是11,得分差11-0=11大于当前running_high=0。这里的nonlocal running_high关键字会直接修改f1绑定的那个announce_highest实例中的running_high变量——把它从0改成了11。随后返回的announce_highest(1, 11, 11)是另一个全新的实例(即f2),和f1的变量环境完全无关。第二次调用f1(生成f2_again):
此时调用f1(11, 9),使用的还是f1绑定的那个announce_highest实例的变量环境——而这个环境里的running_high已经被上一次调用修改成了11,所以你看到的running_high值是11,而非预期的0。
关键误区
你误以为f1每次调用都会重新初始化running_high,但实际上f1是一个绑定了固定变量环境的闭包,nonlocal对变量的修改是永久保存在这个环境里的,后续调用f1时会直接使用修改后的值。
内容的提问来源于stack exchange,提问作者Richard

