本文主要介绍什么是闭包,Ptyhon中使用闭包时容易出现的变量问题。
闭包
闭包指延伸了作用域的函数,其中包含函数定义体中引用、但是不在定义体中定义的非全局变量。函数是不是匿名的没有关系,关键是它能访问定义体之外定义的非全局变量。
举个栗子
1 | def make_averager(): |
上面定义了一个嵌套函数,作用是计算移动平均值。
调用make_averager
时,返回一个averager
函数对象。每次调用averager
时,会把一个新的参数值添加到列表中,然后计算列表的平均值。series
是make_averager
函数的局部变量,但是调用avg(10)
时,make_averager()
函数已经返回了,所以它的本地作用域也没有了。
在averager()
函数中,series
变成了自由变量,就是没在本地作用域中绑定的变量。
所以闭包是一种函数,它会保留定义函数时存在的自由变量的绑定,在调用函数时,虽然定义作用域不可用了但是还可以使用绑定的变量。注意,只有嵌套在其他函数中的函数才可能需要处理不在全局作用域中的外部变量
栗子改进
虽然上面函数可以实现计算移动平均,但是效率不高,因为把所有历史数据都保留在列表中。如果只存储总和以及元素个数,再求平均值更好些。
1 | def make_averager(): |
上述函数在调用averager()
时是有问题的,原因是count
初始定义是数字,是不可变类型。在执行count = count + 1
时,count
变成了局部变量,不是自由变量,所以解析器认为count
在averager()
里没有定义。total
变量也是这样的问题。
问题显然出现在变量是否是可变类型上,在用列表计算的时候,采用的series
是可变的列表类型。但是对于数字、字符串、元祖等不可变类型,嵌套函数内只能读取,不能更新,如果尝试重新绑定,比如count = count + 1
,就会隐式创建局部变量,不会成为自由变量保存在闭包里。
python3下解决方式是引入nonlocal
声明。作用就是把变量标记为自由变量。
1 | def make_averager(): |
总结
在函数内嵌套的函数如果引用了外层函数定义的变量,外部调用嵌套的函数时,可以认为这时候函数变量的作用域延伸了,存在自由变量。主意自由变量如果是不可变类型,需要使用nonlocal
声明。那么闭包有什么用呢?如果要了解Python装饰器,或许会有所帮助。
参考Luciano Ramalho-《Fluent Python》