python 中的闭包

2019-04-21 18:10:52   最后更新: 2019-04-21 22:19:34   访问数量:126




 

此前,我们在介绍 java8 新增的 lambda 表达式时,曾经介绍过“闭包”的概念

java8 新特性

 

所谓的“闭包”,指的就是可以包含自由变量的代码块,代码块中包含的自由变量并没有在定义时绑定任何对象,他们也不是在这个代码块内或任何全局上下文中定义的,而是在代码块环境中定义的局部变量

简单的来说,闭包是一个独立的代码块,但是他可以访问其定义体之外的非全局变量

很多语言通过匿名函数来实现闭包特性,著名的 lambda 表达式就是一个典型的闭包的例子

python 对闭包有着很好的支持

 

假设我们有一个方法,每次调用都输出历史所有调用传入参数的总平均数:

>>> avg(10) 10 >>> avg(11) 10.5 >>> avg(39) 20

 

 

我们如何来实现呢?下面就是一个闭包的例子:

>>> def make_average(): ... series = [] ... def avg(value): ... series.append(value) ... return sum(series)/len(series) ... return avg ... >>> avg = make_average() >>> avg(10) 10.0 >>> avg(11) 10.5 >>> avg(39) 20.0

 

 

在 avg = make_average() 语句执行完成后,make_average 方法的栈空间就会被销毁,而在其栈空间内定义的 series 变量应该会随着 make_average 方法执行的结束而被销毁

但令人意外的是,此后 avg 方法的执行并没有出错,其内部对 series 列表的添加并没有报错,那么 series 变量究竟定义在哪里呢?

此前我们介绍过 python 的作用域,其中提到了 Enclosing 作用域(嵌套函数的外层函数内部) -- 嵌套作用域(闭包)

python 的名称空间与作用域

 

当 python 解释器看到嵌套函数内部使用了外部该局部变量时,解释器会将其标记为自由变量,从而不会随着局部作用域一起被销毁

 

上面的例子我们进一步修改:

>>> def make_average(): ... count = total = 0 ... def avg(value): ... count += 1 ... total += value ... return total / count ... return avg ... >>> avg = make_average() >>> avg(10) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 5, in avg UnboundLocalError: local variable 'count' referenced before assignment

 

 

看上去这个例子与上面闭包的例子没什么区别,他将 series 变量改为了保存总和与调用次数的两个变量,但是却在调用时报错,因为外部 count 与 total 随着 make_average 方法的调用结束而被销毁了,这又是为什么呢?

当解释器看到在嵌套内部的 avg 函数中,对 count 与 total 两个变量均有赋值行为,于是他们被当做了 avg 方法局部作用域中的变量,而不是自由变量,于是外部的两个局部变量就被正常销毁了

python3 引入了 nonlocal 关键字,用于解决这样的问题:

>>> def make_average(): ... count = total = 0 ... def avg(value): ... nonlocal count, total ... count += 1 ... total += value ... return total / count ... return avg ... >>> avg = make_average() >>> avg(10) 10 >>> avg(11) 10.5 >>> avg(39) 20

 

 

>>> class Average: ... def __init__(self): ... self.series = [] ... def __call__(self, value): ... self.series.append(value) ... return sum(self.series)/len(self.series) ... >>> avg = Average() >>> avg(10) 10.0 >>> avg(11) 10.5 >>> avg(39) 20.0

 

 

这个例子通过类实现了对历史调用信息的封装,并通过 __call__ 方法实现了计算逻辑

通常来说,闭包能够实现的功能都可以通过类的方式来实现,类也是通常最容易想到的解决方案,那么,闭包的优势又体现在哪里呢?

在 python 中,闭包最重要的使用方式是在装饰器中,那么,装饰器究竟是什么?闭包与装饰器结合又能碰撞出什么样的火花呢?

我们即将会有一篇文章详尽介绍装饰器的用法与原理,敬请期待

 

欢迎关注微信公众号,以技术为主,涉及历史、人文等多领域的学习与感悟,每周三到七篇推文,全部原创,只有干货没有鸡汤

 

 






技术帖      python      技术分享      作用域      enclosing      闭包      nonlocal     


京ICP备15018585号