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

posted @ 2020-09-19 17:00  孔胡子  阅读(652)  评论(0编辑  收藏  举报