python修饰器(decorator)
转载:
什么是修饰器,为什么叫修饰器
修饰器英⽂是Decorator,
我们假设这样⼀种场景:古⽼的代码中有⼏个很是复杂的函数F1、F2、F3...,复杂到看都不想看,反正我们就是不想改这些函数,但是我们需要改造加功能,在这个函数的前后加功能,这个时候我们很容易就实现这个需求:
def hi():
"""hi func,假装是很复杂的函数"""
return'hi'
def aop(func):
"""aop func"""
print('before func')
print(func())
print('after func')
if__name__ == '__main__':
aop(hi)
以上是很是简单的实现,利⽤Python参数可以传函数引⽤的特性,就可以实现了这种类似AOP的效果。
这段代码⽬前没有什么问题,接下来煎鱼加需求:需求为⼏⼗个函数都加上这样的前后的功能,⽽所有调⽤这些函数地⽅也要相应地升级。
看起来这个需求⽐较扯,偏偏这个需求却是较为⼴泛:在调⽤函数的前后加上log输出、在调⽤函数的前后计算调⽤时间、在调⽤函数的前后占⽤和释放资源等等。
⼀种⽐较笨的⽅法就是,为这⼏⼗个函数逐⼀添加⼀个⼊⼝函数,针对a函数添加⼀个a_aop函数,针对b函数添加⼀个b_aop函数...如此这样。问题也很明显:
1. ⼯作量⼤
2. 代码变得臃肿复杂
3. 原代码有多处调⽤了这些函数,可以会升级不完全
于是接下来有请修饰器出场,修饰器可以统⼀地给这些函数加这样的功能:
def aop(func):
"""aop func"""
def wrapper():
"""wrapper func"""
print('before func')
func()
print('after func')
return wrapper
@aop
def hi():
"""hi func"""
print('hi')
@aop
def hello():
"""hello func"""
print('hello')
if__name__ == '__main__':
hi()
hello()
以上aop函数就是修饰器的函数,使⽤该修饰器时只要在待加函数上⼀⾏加@修饰器函数名即可,如实例代码中就是@aop。
加上了@aop后,调⽤新功能的hi函数就喝原来的调⽤⼀样:就是hi()⽽不是aop(hi),也意味着所有调⽤这些函数的地⽅不需要修改就可以升级。
简单地来说,⼤概修饰器就是以上的这样⼦。
@是个什么
对于新⼿来说,上⾯例⼦中,@就是⼀样奇怪的东西:为什么这样⼦⽤就可以实现煎鱼需求的功能了。
其实我们还可以不⽤@,煎鱼换⼀种写法:
def hi():
"""hi func"""
print('hi')
def aop(func):
"""aop func"""
def wrapper():
"""wrapper func"""
print('before func')
func()
print('after func')
return wrapper
if__name__ == '__main__':
hi()
print('')
hi = aop(hi)
hi()
上⾯的例⼦中的aop函数就是之前说过的修饰器函数。
如例⼦main函数中第⼀次调⽤hi函数时,由于hi函数没叫修饰器,因此我们可以从输出结果中看到程序只输出了⼀个hi⽽没有前后功能。
然后煎鱼加了⼀个hi = aop(hi)后再调⽤hi函数,得到的输出结果和加修饰器的⼀样,换⾔之:
@aop 等效于hi = aop(hi)
因此,我们对于@,可以理解是,它通过闭包的⽅式把新函数的引⽤赋值给了原来函数的引⽤。
有点拗⼝。aop(hi)是新函数的引⽤,⾄于返回了引⽤的原因是aop函数中运⽤闭包返回了函数引⽤。⽽hi这个函数的引⽤,本来是指向旧函数的,通过hi = aop(hi)赋值后,就指向新函数了。
被调函数加参数
以上的例⼦中,我们都假设被调函数是⽆参的,如hi、hello函数都是⽆参的,我们再看⼀眼煎鱼刚才的写的修饰器函数:
def aop(func):
"""aop func"""
def wrapper():
"""wrapper func"""
print('before func')
func()
print('after func')
return wrapper
很明显,闭包函数wrapper中,调⽤被调函数⽤的是func(),是⽆参的。同时就意味着,如果func是⼀个带参数的函数,再⽤这个修饰器就会报错。
@aop
def hi_with_deco(a):
"""hi func"""
print('hi' + str(a))
if__name__ == '__main__':
# hi()
hi_with_deco(1)
就是参数的问题。这个时候,我们把修饰器函数改得通⽤⼀点即可,其中import了⼀个函数(也是修饰器函数):
from functools import wraps
def aop(func):
"""aop func"""
@wraps(func)
def wrap(*args, **kwargs):
print('before')
func(*args, **kwargs)
print('after')
return wrap
@aop
def hi(a, b, c):
"""hi func"""
print('test hi: %s, %s, %s' % (a, b, c))
@aop
def hello(a, b):
"""hello func"""
print('test hello: %s, %s' % (a, b))
if__name__ == '__main__':
hi(1, 2, 3)
hello('a', 'b')
这是⼀种很奇妙的东西,就是在写修饰器函数的时候,还⽤了别的修饰器函数。那也没什么,毕竟修饰器函数也是函数啊,有什么所谓。带参数的修饰器
思路到了这⾥,煎鱼不禁思考⼀个问题:修饰器函数也是函数,那函数也是应该能传参的。函数传参的话,不同的参数可以输出不同的结果,那么,修饰器函数传参的话,不同的参数会怎么样呢?
其实很简单,修饰器函数不同的参数,能⽣成不同的修饰器啊。
如,我这次⽤这个修饰器是把时间⽇志打到test.log,⽽下次⽤修饰器的时候煎鱼希望是能打到test2.lo
g。这样的需求,除了写两个修饰器函数外,还可以给修饰器加参数选项:
from functools import wraps
def aop_with_param(aop_test_str):
def aop(func):
"""aop func"""
@wraps(func)
def wrap(*args, **kwargs):
print('before ' + str(aop_test_str))
func(*args, **kwargs)
print('after ' + str(aop_test_str))
return wrap
return aop
@aop_with_param('abc')
def hi(a, b, c):
"""hi func"""
print('test hi: %s, %s, %s' % (a, b, c))
@aop_with_param('pppppp')
def hi2(a, b, c):
"""hi func"""
print('test hi: %s, %s, %s' % (a, b, c))
if__name__ == '__main__':
hi(1, 2, 3)
python新手代码例子
print('')
hi2(2, 3, 4)
同样的,可以加⼀个参数,也可以加多个参数,这⾥就不说了。
修饰器类
⼤道同归,逻辑复杂了之后,⼈们都喜欢将函数的思维层⾯抽象上升到对象的层⾯。原因往往是对象能拥有多个函数,对象往往能管理更复杂的业务逻辑。
显然,修饰器函数也有对应的修饰器类。写起来也没什么难度,和之前的⽣成器⼀样简单:
from functools import wraps
class aop(object):
def__init__(self, aop_test_str):
self.aop_test_str = aop_test_str
def__call__(self, func):
@wraps(func)
def wrapper(*args, **kwargs):
print('before ' + self.aop_test_str)
func()
print('after ' + self.aop_test_str)
return wrapper
@aop('pppppp')
def hi():
print('hi')
看得出来,这个修饰器类也不过是多了个__call__函数,⽽这个__call__函数的内容和之前写的修饰器函数⼀个样!⽽使⽤这个修饰器的⽅法,和之前也⼀样,⼀样的如例⼦中的@aop('pppppp')。
甚⾄,煎鱼过于⽆聊,还试了⼀下继承的修饰器类:
class sub_aop(aop):
def__init__(self, sub_aop_str, *args, **kwargs):
self.sub_aop_str = sub_aop_str
super(sub_aop, self).__init__(*args, **kwargs)
def__call__(self, func):
@wraps(func)
def wrapper(*args, **kwargs):
print('before ' + self.sub_aop_str)
super(sub_aop, self).__call__(func)()
print('after ' + self.sub_aop_str)
return wrapper
@sub_aop('ssssss', 'pppppp')
def hello():
print('hello')
if__name__ == '__main__':
hello()
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论