• python闭包和装饰器


    本文目录:

    1. 闭包的解析和用法

    2. 函数式装饰器

    3. 类装饰器

    一、闭包

    闭包是一种函数,从形式上来说是函数内部定义(嵌套)函数,实现函数的扩展。在开发过程中,考虑到兼容性和耦合度问题,如果想在原有的函数基础上添加东西而又不改动原有函数的结构,通常会使用闭包。但闭包的功能还不只是这个。实际上,闭包会保留定义函数时存在的自由变量的绑定,这样在调用函数时,虽然定义作用域不可用了,但是仍然可以使用那些绑定的变量。简单来说,普通函数在调用完后,函数内部的变量就释放了(因为直接调用的函数没有绑定在某一个对象上,Cpython解析器就会把它回收了),而闭包内部的变量仍然保存着。

    普通函数不会保存变量的值:

    例如:
    def function(value):
        nums = []
        nums.append(value)
        return nums
    
    func = function(2)
    func2 = function(3)
    print(func)     # [2]
    print(func2)    # [3]
    两次调用函数返回的值都是不相同的

    在闭包中,外部函数的变量一直会为内部函数“保留”着,每次调用函数都可以获取这些变量

    def closure():
        nums =[]
        def function(value):
            nums.append(value)
            return nums
        return function
    
    close = closure()
    close(5)
    close(6)
    close(7)
    # 元组形式返回嵌套函数function的变量
    print(close.__code__.co_varnames)       #('value',)
    print(close.__code__.co_freevars)        #('nums',)
    # 列表形式保存嵌套函数中自由变量的值
    print(close.__closure__[0].cell_contents)      #[5, 6, 7]

    闭包的执行顺序可以理解为:

    closure(function(value))

    因此close = closure()相当于为闭包创建了一个绑定的对象,这个对象内部有变量nums和函数function。当变量在下一次调用的时候,这些量还会保存着。因此当多次调用close()的时候,nums列表会更新。

    以上代码的解析:

    对象审查(反射)

    _ _code_ _返回对象中的函数

    _ _co_varnames 返回内部函数中保存的变量,例如function中的value

    _ _co_freenames 返回内部函数中的自由变量,自由变量是编程中的一个专业名词。

    如上面的代码中,nums并不是在function函数中绑定的,它是在它的外部函数的作用域范围内绑定的,所以在function内部,nums是一个自由变量。而close对象为function函数保留了这个自由变量,在每次调用函数时,都可以更新这个自由变量。

    自由变量(所有的)实际保存在闭包中,可以通过_ _closure_ _来获取,它是一个列表,每个元素都表示一个自由变量,如上面的nums。每个元素都是一个cell,它的属性cell_contents保存着这个自由变量的值,因此有:

    _ _closure_ _[0].cell_contents

    更多关于函数/类/生成器的审查可以参考官方文档:

    https://docs.python.org/2/library/inspect.html

    二、函数形式的装饰器

    上面讲了如何通过嵌套函数实现一个闭包,下面将装饰器是如何实现的。实际上,装饰器离不开对闭包的理解,函数形式的装饰器看起来像是闭包换了一种表达形式,调用起来更灵活和更方便。

    例如:

    # 函数形式的闭包
    registry = []
    
    def decorator(func):
        print('registe %s'%func)
        registry.append(func)
        return func              # 返回的量必须是一个函数,否则会报错
    
    @decorator
    def fun1():
        print('running fun1')
    
    @decorator
    def fun2():
        print('running fun2')
    
    @decorator
    def fun3():
        print('running fun3')
    
    fun1()
    fun2()
    fun3()

    结果:

    registe <function fun1 at 0x000002234D891378>
    registe <function fun2 at 0x000002234D891400>
    registe <function fun3 at 0x000002234D891488>
    running fun1
    running fun2
    running fun3

    装饰器看起来有点像闭包,只不过是加了一个@的外壳,而这个外壳函数的参数必须是一个函数,并且必须要有返回函数(返回的一般是内部函数)

    装饰器的执行顺序:

    decorator(func)

    内部函数的参数可以在函数调用时传入,而不必像闭包那样必须由对象传入。

    值得注意的是:装饰器函数有导入时运行和运行时运行的区别,装饰器在模块导入的时候就执行了,而被装饰的函数则在调用的时候才执行。

    上面这个被装饰器“装饰”的函数似乎看起来跟装饰器“互动”很少,那么下面结合装饰器和闭包实现一个更复杂的装饰器:

    def decorator(func):
        def outerFunc(*args):      # 装饰器内部实现闭包,闭包的外部函数接受任意定位变量
            outerFunName = outerFunc.__name__
            innerFunName = func.__name__
            print('change innerFunc:%s to outerFunc:%s'%(innerFunName, outerFunName))
            result = func(*args)            # 可以实现,因为闭包中保存了自由变量func
            result += " and start running"
            return result
        return outerFunc          # 将改变返回的函数,返回外部函数
    
    @decorator
    def fun(str):
        return str
    
    str = fun('This is funciton1')   # change innerFunc:fun to outerFunc:outerFunc
    print(str)                      # This is funciton1 and start running

    装饰器执行顺序:

    decorator(outerFunc(func(args)))

    这个装饰器内部的闭包实现还是比较简单的,只是为了说明原理,在编程过程中可以根据实际添加更多的功能实现。

    继续改造,让装饰器也带上参数

    # 带参数装饰器
    def decorator(name):
        def _decorator(func):
            def outerFunc(*args):
                print(name)
                outerFunName = outerFunc.__name__
                innerFunName = func.__name__
                print('change innerFunc:%s to outerFunc:%s'%(innerFunName, outerFunName))
                result = func(*args)
                result += " and start running"
                return result
            return outerFunc
        return _decorator
    
    @decorator(name='@Author:Tom')
    def fun(str):
        return str
    
    str = fun('This is funciton1')
    print(str)
    
    结果:
    @Author:Tom
    change innerFunc:fun to outerFunc:outerFunc
    This is funciton1 and start running

    三、类形式的装饰器

    讲完了函数形式的装饰器,那么接下来讲讲类形式的装饰器

    一般类的定义如下:

    class Test(object):
        def __init__(self,name):
            self._m = 0
            self._n = 0
            self.name = name
    
        def test_print(self):  
            print(self.name)

    而如果想要将一个类变成一个装饰器,那么就需要一个很关键的魔法方法_ _call_ _(),它的作用是将一个类实例变成可调用的,改造一下上面的类:

    class Test(object):
        def __init__(self):
            self.count = 0
    
        def __call__(self):
            # print(self.count)      
            self.count += 1         # 每一次调用这个类实例都记录一次
            return self.count
    
    # __call__函数将类实例变成可调用形式,而实际上还会有一个返回量(变量/函数),因此需要写return,否则返回为None
    test = Test()
    # 以函数调用的形式直接调用类实例
    print(test())    # 1
    print(test())    # 2

    这样看起来,类形式的装饰器有点像函数形式的装饰器,它也保存了一些变量。实际上这点不足为奇,因为,本来类实例的变量已经绑定在类实例对象中。

    还可以这样用:

    class Average:
        def __init__(self):
            self.values = []
    
    # 每次调用average实例都会更新self.values
        def __call__(self, newvalue):
            self.values.append(newvalue)
            total = sum(self.values)
            average = total / len(self.values)
            return average
    
    average = Average()
    print(average(6))
    print(average(7))
    print(average(8))
    
    结果:
    6.0
    6.5
    7.0

    类装饰器:

    class Decorator:
        def __init__(self, add=1):  # 定义可以传入的参数
            self.count = 0
            self.add = add
    
        def __call__(self, fun):
            self.fun = fun
            return self._call_func
    
        def _call_func(self):
            self.count += self.add
            return self.fun(self.count)
    
    
    # 相当于Decorate(count)
    @Decorator(add=2)   # 改变传入的参数值
    def count(cnt):
        print(cnt)
    
    count()   # 2
    count()   # 4

    笔者认为类形式的装饰器会比函数形式的装饰器更加灵活和方便,因为它的内部实现可以更灵活,看起来也比较符合日常使用的习惯,因为函数式的装饰器看起来总有一点怪怪的(笔者本人看法而已)。实际使用中就要根据业务需求来选择了

    参考文章:

    1. 《流畅的python》

    2.  https://docs.python.org/2/library/inspect.html

  • 相关阅读:
    查询论文引用次数及格式和相似论文的方法
    JAVA日期加减运算
    luogu2833 等式
    luogu2261 [CQOI2007] 余数之和
    luogu2822 组合数问题
    luogu3942 将军令 贪心
    luogu3941 入阵曲
    luogu3939 数颜色
    二分查找总结
    luogu3938 斐波那契
  • 原文地址:https://www.cnblogs.com/thomson-fred/p/10390589.html
Copyright © 2020-2023  润新知