zoukankan      html  css  js  c++  java
  • python 装饰器

    装饰器背景

    装饰器的是一种AOP切面编程思想,可以将核心代码从冗长的业务代码中剥离出来,常见的打日志例子:

    def log():
        import inspect
        print(f'called by {inspect.stack()[1][3]}...')
    
    def add():
        log()
        print('add...')
    
    def multiplication():
        log()
        print('multiplication...')
    
    if __name__ == '__main__':
        multiplication()
        add()
    
    called by multiplication...
    multiplication...
    called by add...
    add...
    

    这里log()为了举例并不复杂,但是如果业务更复杂,可能会将核心代码add() multiplication()“淹没”且会重复写某些代码。而这就是出发点,孕育出了装饰器的思想。装饰器,从字面意思也可意会到其涉及想法,起到一个装饰的作用,而被装饰者必然是核心业务代码

    装饰器小例

    针对上面的例子,我们配合装饰器修改一下:

    def wapper(fun):
        def log():  # 返回该方法, 该方法中包含 fun()——核心逻辑代码
            import inspect
            print(f'called by {fun.__name__}...')
            fun()
        return log
    
    def add():
        print('add...')
    
    def multiplication():
        print('multiplication...')
    
    if __name__ == '__main__':
        add = wapper(add)  # 返回被 wapper 包装过的 add 方法. 取成 add 的名字罢了
        mul = wapper(multiplication)
    
        add()  
        mul()
    
    called by add...
    add...
    called by multiplication...
    multiplication...
    

    带参数的装饰器

    那么同样的,如果需要带参数,也只要简单的修改一下:

    def wapper(fun):
        def log(*args, **kwargs):
            import inspect
            print(f'called by {fun.__name__}...')
            fun(*args, **kwargs)  # 根据传入的参数按个数前后顺序匹配 add(a,b,l) 参数
        return log
    
    def add(a, b, l):
        print(f'add...{a+b}')
        for i in l:
            print(i)
    
    
    if __name__ == '__main__':
        add = wapper(add)
    
        add(4,7, [9,99])  # 调用wapper中的log函数
    
    called by add...
    add...11
    9
    99
    

    照上个例子再套一层,其实没有什么意义,就是方便更加充分理解(●ˇ∀ˇ●):
    关于闭包nonlocal,可点击查看nonlocal, 查看闭包

    def wapper(msg):
        def inner_wapper(fun):
            nonlocal msg # 闭包 
            msg += 1
            print(msg)
            def log(*args, **kwargs):
                import inspect
                print(f'called by {fun.__name__}...')
                fun(*args, **kwargs)
            return log
        return inner_wapper
    
    def add(a, b, l):
        print(f'add...{a+b}')
        for i in l:
            print(i)
    
    10
    called by add...
    add...11
    9
    99
    

    @语法糖——带参数

    了解了上文的几个例子,为了写的简便一些可以使用 @ ——只是为了写的更方便,其实我还是喜欢上文的更简单直观

    def wapper(msg):
        def inner_wapper(fun):
            nonlocal msg # 闭包 
            msg += 1
            print(msg)
            def log(*args, **kwargs):
                import inspect
                print(f'called by {fun.__name__}...')
                fun(*args, **kwargs)
            return log
        return inner_wapper
    
    @wapper(9)  # 添加装饰器
    def add(a, b, l):
        print(f'add...{a+b}')
        for i in l:
            print(i)
    
    if __name__ == '__main__':
        add(4,7, [9,99])
    

    @语法糖——不带参数

    def wapper(fun):
        def log(self):
            import inspect
            fun(self)
            print(f'_x = {self._x}')
        return log
    
    class my:
        def __init__(self):
            self._x = 963
    
        # @staticmethod
        @wapper
        def te(self):  # 这里的参数要和log处的参数对准——其实待会直接调用log了
            print('==te...')
    
    cla = my()
    cla.te()
    
    ==te...
    _x = 963
    

    总结:带参数的wapper需要多写一层,不带的则直接理解层wapper(func) 其中func就是被修饰的核心方法

    类装饰器

    类装饰器其实和上文的装饰器没有多大区别。如果A是一个函数对象,A()调用函数,如果是一个类对象A()自然调用__call__()方法。所以如下例子:

    class wapper:
        def __init__(self, fun, msg):
            self.msg = msg
            self.fun = fun
            print(self.msg)
    
        def __call__(self, *args, **kwargs):
            msg, = args
            print(msg)
            def log(*args, **kwargs):
                print(f'called by {self.fun.__name__}...')
                return self.fun(*args, **kwargs)
            return log
    
    def add(a, b, l):
        print(f'add...{a+b}')
        for i in l:
            print(i)
    
    if __name__ == '__main__':
        add = wapper(add, 'hi here1')('__call__ 调用中')
        print('=======')
        add(4,7, [9,99])
    
    hi here1
    __call__ 调用中
    =======
    called by add...
    add...11
    9
    99
    

    @形式的类装饰器

    这种形式只需要注意,如果类装饰器本身不需要传入参数,如下 @wapper。执行过程:当调用main中的add方法时,先初始化wapper,可以看到打印了function的名字add,随后add(4,7, [9,99]) 其实执行__call__方法:

    class wapper:
        def __init__(self, fun):
            self.fun = fun
            print(self.fun.__name__)
    
        def __call__(self, *args, **kwargs):
            print('hi here2')
            print(f'called by {self.fun.__name__}...')
            return self.fun(*args, **kwargs)
    
    @wapper
    def add(a, b, l):
        print(f'add...{a+b}')
        for i in l:
            print(i)
    
    if __name__ == '__main__':
        add(4,7, [9,99])
    
    add
    hi here2
    called by add...
    add...11
    9
    99
    

    另一种情况是,当wapper本身传入参数初始化:@wapper(...)初始化wapper, call__传入fun
    这种情况可能看上去有点绕,如下:@wapper('hi here1')初始化,随后add(4,7,[9,99])先调用__call

    class wapper:
        def __init__(self, msg):
            self.msg = msg
            print(self.msg)
    
        def __call__(self, fun):
            print('hi here2')
            def log(*args, **kwargs):
                print(f'called by {fun.__name__}...')
                return fun(*args, **kwargs)
            return log
    
    @wapper('hi here1')  # 传参数
    def add(a, b, l):
        print(f'add...{a+b}')
        for i in l:
            print(i)
    
    if __name__ == '__main__':
        add(4,7, [9,99])
    
    hi here1
    hi here2
    called by add...
    add...11
    9
    99
    

    内置装饰器@property背景

    对于类中的属性,我们对其get, set, del 则情况如下:

    class my:
        def __init__(self):
            self._x = None
    
        def setx(self, x):
            print('==set..')
            self._x = x
    
        def getx(self):
            print('==getx...')
            return self._x
    
        def delx(self):
            print('==delx...')
            del self._x
    
    cla = my()
    cla.setx(99)
    print(cla.getx())
    cla.delx()
    
    ==set..
    ==getx...
    99
    ==delx...
    

    写法一:内置property

    类似Java的,同样python也提供了对类属性的基本操作:
    使用内置proper方法,我们不需要再同上例一样调用setx()来处理,使用cla.p即可

    class my:
        def __init__(self):
            self._x = None
    
        def setx(self, x):
            print('==set..')
            self._x = x
    
        def getx(self):
            print('==getx...')
            return self._x
    
        def delx(self):
            print('==delx...')
            del self._x
        p = property(getx, setx, delx)  # P 只是一个别名了
    
    cla = my()
    cla.p = 99  # p 就当作属性 
    print(cla.p)
    del cla.p
    
    ==set..
    ==getx...
    99
    ==delx...
    

    写法二:内置property(常用)

    还有一种更简便的写法:
    两个注意点,对于变量的命名改成一致的, 如下te,其次是顺序setter 和 deleter 写在后面

    class my:
        def __init__(self):
            self._x = None
    
        @property  # 写在getter这. 且再setter deleter之前
        def te(self):
            print('==getx...')
            return self._x
    
        @te.setter
        def te(self, x):
            print('==set..')
            self._x = x
    
        @te.deleter
        def te(self):
            print('==delx...')
            del self._x
    
    
    cla = my()
    cla.te = 99
    print(cla.te)
    del cla.te
    
    ==set..
    ==getx...
    99
    ==delx...
    

    其实最常用的是只写@property 而不写 setter 和 delter 这样就使得变量只能只读了

    class my:
        def __init__(self):
            self._x = 991
    
        @property  # 对_x只读
        def te(self):
            print('==getx...')
            return self._x
    
    cla = my()
    print(cla.te)
    # cla.te = 99 
    # del cla.te
    
    ==getx...
    991
    

    TypeError: 'staticmethod' object is not callable

    当对某个function使用多个装饰器的时候(包括@staticmethod),可能会报错。首先明确加,加载顺序是从下自上的,如下例(会报错), 先@staticmethod后@wapper

    def wapper(fun):
        def log():
            fun()
            print(f'_x = ')
        return log
    
    class my:
        def __init__(self):
            self._x = 963
    
        @wapper
        @staticmethod  # 更正两个的顺序可避免错误
        def te():
            print('==te...')
    
    my.te()
    
    TypeError: 'staticmethod' object is not callable
    

    关于错误的原因,我们线看@staticmethod源码:

    class staticmethod(object):
        def __init__(self, function): # real signature unknown; restored from __doc__
            pass
        ...
    

    可以看到是一个类,init初始化传入的是function, 但是该类是没有_call_,所以当执行@wapper的时候——即def wapper 中的fun(),因为没有staticmethod没有call()方法,所以报错。更改错误只需要更改wapper的顺序即可。
    比如我下面的举例,含有call方法,所以可以避免错误

    def new_te(msg):
        print('新版te function', msg)
    
    def wapper(cla):
        cla.fun = new_te
        return cla
    
    class my_static:
        def __init__(self, fun):
            print('my_static init...')
            self.fun = fun
    
        def __call__(self, *args):
            msg, = args
            print('==my_static...', msg)
            self.fun(msg)
    
    @wapper
    @my_static
    def te(msg):
        print('==te...', msg)
    
    cla = te('i am fond of it')  # te 其实已经被包装成my_static类了,随后wapper替换新的self.fun
    
    my_static init...
    ==my_static... i am fond of it
    新版te function i am fond of it
    

    其中@my_static def te(msg): 两句话就会将my_static(te)传入init。
    cla = te('i am fond of it') 则init和call都执行了
    同cla = te 比较,就可以看出差别。

    参考

    https://www.cnblogs.com/cicaday/p/python-decorator.html
    https://www.imooc.com/article/50647
    https://blog.csdn.net/u013965862/article/details/100662820
    https://blog.csdn.net/frank_abagnale/article/details/82143855

  • 相关阅读:
    kubectl命令行工具
    资源编排(YAML)
    vscode自定义vue模板代码
    vscode10个必装的插件
    【转】Android系统开篇
    Android应用资源分析(老罗链接整理)
    APK优化工具zipalign的详细介绍和使用
    Android中APK签名工具之jarsigner和apksigner详解
    Android反编译和二次打包
    python修饰器(装饰器)以及wraps
  • 原文地址:https://www.cnblogs.com/KongHuZi/p/13696504.html
Copyright © 2011-2022 走看看