简谈 Python3 关键字 nonlocal 使用场景
下面是之前提过的有待提升效率的计算移动平均的方法:
我们在文章简谈Python3中的闭包中设计的计算移动平均的方法效率并不高,原因是我们存储了所有的历史数据在列表中,然后在每次调用averager时使用sum求和。要实现同样的功能,更好的实现方法是只存储当前的总值和元素个数,使用这两个值计算移动平均值即可。
直观来思考,我们可以对代码进行如下改进(注意:代码有缺陷!)
尝试使用该函数,会得到如下的结果:
提示错误为变量count在赋值前进行了引用,实际上,total也存在相同的问题,只是count变量处提前抛出了UnboundLocalError异常。接下来进一步解释,首先我们需要明白一个前提,在Python中,对于一个不可变数据类型比如上述示例中的count,count+=1和count=count+1是等效的。对于可变数据类型的讨论,可以参考文章浅析Python中列表操作之*和*= 。
因此,我们在averager的定义体中为count赋值了,这会把count变成局部变量,total变量也受这个问题影响。
先前版本没遇到这个问题,因为我们没有给series赋值,我们只是调用series.append,并把它传给sum和len。也就是说,我们利用了列表是可变的对象
这一事实。
但是对数字、字符串、元组等不可变类型来说,只能读取,不能更新。如果尝试重新绑定,例如count=count+1,其实会隐式创建局部变量count。这样,count就不是自由变量了,因此不会保存在闭包中。
为了解决这个问题,Python3引入了nonlocal
声明。它的作用是把变量标记为自由变量
,即使在函数中为变量赋予新值了,也会变成自由变量。如果为nonlocal声明的变量赋予新值,闭包中保存的绑定会更新。最新版make_averager的正确实现如下:
在Python2中没有nonlocal关键字。如果要实现上面的功能需要变通的方法。基本上,这种处理方式是把内部函数需要修改的变量(如count和total)存储为可变对象(如字典或简单的实例)的元素或属性,并且把那个对象绑定给一个自由变量。
至此,我们了解了Python闭包,接下来可以使用嵌套函数正式实现装饰器了。
推荐阅读:
简述Python中变量作用域的规则zhuanlan.zhihu.com
简谈Python3中的闭包zhuanlan.zhihu.com
浅析Python中列表操作之*和*=zhuanlan.zhihu.com
浅析Python中的列表和元组zhuanlan.zhihu.com
版权声明: 本文为 InfoQ 作者【王坤祥】的原创文章。
原文链接:【http://xie.infoq.cn/article/94332dc131cecf6fca27165b8】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论