为何列表不触发UnboundLocalError?及make_monitored函数实现疑问
问题解答
嘿,我来帮你搞定这两个问题~
一、为啥用列表时不会触发UnboundLocalError?
先给你看个典型的报错场景,很多人刚学Python都会踩这个坑:
count = 0 def add_count(): count += 1 # 运行这里直接跳UnboundLocalError
为啥会报错?因为Python的作用域规则是这样的:如果你在函数内部给一个变量直接赋值(比如count = ...,count +=1本质也是赋值),Python会默认把这个变量当成函数的局部变量。上面的代码里,count +=1相当于count = count +1,Python找局部作用域里的count,发现没定义,就直接报错了。
那换成列表为啥就没事?看例子:
count = [0] def add_count(): count[0] += 1 # 完美运行,不会报错
因为列表是可变对象,我们这里不是给count变量重新绑定新值(比如count = [1]才是重新绑定),而是修改列表内部的元素。这时候Python会先在局部找count,找不到就去外层作用域找,找到后直接改它的元素,不会把count标记成局部变量,自然就不会触发UnboundLocalError了。
总结一下:可变对象的原地修改(比如改列表元素、字典值)不会改变变量本身的绑定,所以不会被判定为局部变量;而直接给变量赋值会改变绑定,触发局部变量判定,没提前定义就会报错。
二、实现make_monitored函数
我用闭包写了一个符合要求的版本,还加了测试示例,你可以直接参考:
def make_monitored(f): call_count = 0 # 用闭包维护内部计数器,外面拿不到,只能通过mf操作 def mf(input_arg): nonlocal call_count # 告诉Python:这个变量不是局部的,用外层函数的那个 if input_arg == "how-many-calls?": return call_count elif input_arg == "reset-count": call_count = 0 return "计数器已重置为0" # 加个提示,方便知道操作成功 else: call_count += 1 return f(input_arg) # 调用原函数,返回结果 return mf
测试一下效果:
比如我们拿一个简单的平方函数来测试:
def square(x): return x * x # 把square变成被监控的函数 mf_square = make_monitored(square) print(mf_square(3)) # 输出9,此时调用次数是1 print(mf_square(4)) # 输出16,调用次数变成2 print(mf_square("how-many-calls?")) # 输出2,正确返回计数 print(mf_square("reset-count")) # 输出"计数器已重置为0" print(mf_square("how-many-calls?")) # 输出0,重置成功
小补充:
如果是用Python2的话,没有nonlocal关键字,那我们可以用第一个问题里的列表技巧,把计数器放进列表里:
def make_monitored(f): call_count = [0] # 用列表存计数器,避免UnboundLocalError def mf(input_arg): if input_arg == "how-many-calls?": return call_count[0] elif input_arg == "reset-count": call_count[0] = 0 return "计数器已重置为0" else: call_count[0] += 1 return f(input_arg) return mf
这样Python2环境也能正常运行~
内容的提问来源于stack exchange,提问作者Prashin Jeevaganth
相关产品推荐
相关产品推荐

