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