闭包closure——没有闭包,装饰器的功能会大打折扣

Python的装饰器是一大利器,几乎所有的流行框架,都在广泛使用装饰器

为什么说没有闭包( closure) ,装饰器的功能会大打折扣?

一个最简易的装饰器是什么都不不做,只是透传一个函数对象

def final(f):
      return f

装饰器的原理是

@deco
def func():
      ....
func() # call

等价于

def func():
      ...
func = deco(func) # 注意这里
func() 

func = deco(func) 右边的表达式有可能偷梁换柱把函数换成了另外一个函数,有可能没有换,只是在对func对象做了一些事情,比如加一点属性之类的,也可以只是在func调用之前增加一些别的事情。

deco(func) 内部做什么,灵活性极大
这也是装饰器的特点之一——不需要修改func的定义和实现,既可以灵活地操纵它的调用行为,借用动态语言的特性,既可以动态注入属性,也可以添加想要的逻辑,很灵活。

更多的一种装饰器是——偷梁换柱。
即经过修饰之后函数已经不是原来那个。可能在装饰器器内部已经计算出结果,同时转换成另外一个函数。

这个“另外”的函数通常是通过闭包来提供的。

def deco(func):
      def wrapper():
            ret = func()
            return ret
      return wrapper 

闭包在python中有一个 __closure__ 协议

代码

def advance_avg():
    ls = [] # 只初始化一次
    s = 1

    def wrapper(num):
        nonlocal s # 必须要声明 nolocal 否则会报告未知属性错误
        s += num
        print(f"s is {s}")
        ls.append(num)
        total = sum(ls)
        return total / len(ls)
    return wrapper

当调用一次 advance_avg() 的时候,s和ls都会绑定在对象上

adv = advance_avg() # int 和 list对象都已经绑定

以后每次调用adv(val) 绑定的ls和s都可以被内部的函数wrapper操作。而且一大特性是这两个变量可以缓存上一次调用之后计算的结果,且看

adv = advance_avg()
adv(1) # 结果是1
adv(2) # 结果是1.5 不是2
adv.__closure__ # 输出 <cell at 0x7f8da8143520: list object at 0x7f8d9801db40>, <cell at 0x7f8da8143910: int object at 0x108f92ae0>

adv的 __closure__ 属性输出了一个int 和 list对象地址,这两个对象的生命周期跟随adv 。

理解闭包关键的地方在于作用域的概念。

有个可能不太精确的类比
对于嵌套闭包,我们可以把最上层的函数定义视为一个类定义,在python 中一切皆对象,包括函数

对于类的写法,我们同样可以写一个上述用起来一模一样的功能类,只是需要在类内部定义一下__call__ —— 大佬推荐的工程级别的装饰器写法,用定义了__call__ 函数的类

class Average:
    def __init__(self):
        self.series = []
        self.s = 0 

    def __call__(self, new_value):
        self.series.append(new_value)
        total = sum(self.series)
        return total / len(self.series)

这个类用起来跟上面的闭包函数形式上没有太大的区别。
__init__ 函数有一次性声明并且追随Average对象声明周期的两个变量,这个和闭包一样,带闭包的 advance_avg函数对象保持着一个int和一个list的生命周期

总结

  • 类的方法,可以访问类内的self属性
  • 方法中的方法(闭包)可以访问上层方法定义的局部属性(类比与类内定义的self 方法)
  • 函数可以多层嵌套,访问范围是内层有外层的访问权,但是反过来不行——否则封装毫无意义
  • 闭包访问上层局部变量,如果变量是不可变对象,一定要使用 nolocal声明,否则会报告未知属性错误
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容