每天5分钟玩转Python(15) - 闭包
闭包跟函数式紧密联系在一起的,介绍闭包之前先了解一下python中函数的高阶用法。比如嵌套函数、变量作用域等等。
变量作用域
变量作用域是程序运行时变量可被访问的范围,定义在函数内的变量是局部变量, 局部变量的作用范围只能是函数内部范围内,它不能在函数外引用。
1 | def test(): |
定义在模块最外层的变量是全局变量,它是全局范围内可见的,当然在函数里面也可以读取到全局变量的。例如:
1 | num = 10 # 全局变量 |
再来一个示例
1 | outer = 6 |
执行这个示例会报错,Python在编译函数时,发现对 outer 有赋值的操作,它判定 outer 是一个局部变量, 所以在打印 outer 时,它会去查询局部变量outer,发现并没有赋值,所以会抛出异常。
Python 就是这样设计的,它认为如果在函数体中对变量有赋值操作,则证明这个变量是一个局部变量, 并且它只会从局部变量中去读取数据。这样设计可以避免对全局变量的误操作。
可以使用 global
关键字来解决这个问题:
1 | outer = 6 |
嵌套函数
函数不仅可以定义在模块的最外层,还可以定义在另外一个函数的内部, 像这种定义在函数里面的函数称之为嵌套函数(nested function)例如:
1 | def print_msg(): |
闭包
当我们在函数内定义一个函数时,如果这个内部函数使用了外部函数的临时变量, 且外部函数的返回值是内部函数的引用时,那么返回的这个内部函数引用就是一个闭包。
有点绕口,直接上代码比较直观:
1 | def simple_avg(): |
nonlocal 关键字
上面的代码有一个小缺陷,就是有很多重复的计算,当我们传入一个新的值想要得到新的平均值时, 总是先将列表中所有值都计算求和,再求平均值,其实已经求和过的结果可以保存起来, 下一次只需要加最新的那个数就可以了。一般我们会想到这样修改:
1 | def simple_avg(): |
运行后报错:
1 | UnboundLocalError: local variable 'scores' referenced before assignment |
这里报错的原因是因为 scores += val
,有了赋值操作,则认为 scores
是局部变量了。
而我们也没办法使用 global
关键字,因为此时 score
s 和 count
是定义在 get_avg
函数内的,
它们俩也是一个局部变量。而为什么我们使用 list 时,没有出现这个问题呢?也是很好理解的,
因为我们使用的是 list.append()
方法,它没有赋值操作。
在 Python 3 中引入了一个关键词 nonlocal 解决了这一个问题:
1 | def simple_avg(): |
结果正确返回。
闭包的应用
应用最多的就是装饰器,这个会在后面专门讲装饰器。
另外还有一个比较常用的应用就是惰性求值,具体的可以看下Django里面QuerySet的实现源码。