python 装饰器
用于函数的装饰器
为什么要用函数装饰器
Python 装饰允许在不修改原有函数代码的基础上,动态地增加或修改函数的功能,装饰器本质上是一个接收函数作为输入并返回一个新的包装过后的函数的对象。
无参函数装饰器
def hello(fn):
def wrapper(*args,**kwargs):
print("start hello")
result = fn(*args,**kwargs)
print("end hello")
return result
print("in hello")
return wrapper
@hello
def hello_tom():
print(f"hello_tom {hello_tom.__name__=}")
def main():
hello_tom()
pass
if __name__ == "__main__":
main()
上述代码中输出结果如下:
in hello
start hello
hello_tom hello_tom.__name__='wrapper'
end hello
上面输出中有两个疑惑:
1.为什么会先输出in hello
2.hello_tom中的函数名字为什么打印出来是wrapper
下面我们来先解决第一个问题
其实装饰器的执行发生在函数运行之前,即使你没有调用目标函数装饰器也会执行如下:
def hello(fn):
def wrapper(*args,**kwargs):
print("start hello")
result = fn(*args,**kwargs)
print("end hello")
return result
print("in hello")
return wrapper
@hello
def hello_tom():
print(f"hello_tom {hello_tom.__name__=}")
def main():
# hello_tom()
pass
if __name__ == "__main__":
main()
这段代码可以看出main函数没有执行任何语句,但是运行时会发现仍然有输出:
in hello
下面我们来解决第二个问题
我们打印的函数名称不是hello_tom而是wrapper,是因为被装饰器修饰之后,原始函数的签名被破坏了。如果我们想要不被破坏可以使用functools.wraps
下面我们再修改下代码:
from functools import wraps
def hello(fn):
@wraps(fn)
def wrapper(*args,**kwargs):
print("start hello")
result = fn(*args,**kwargs)
print("end hello")
return result
print("in hello")
return wrapper
@hello
def hello_tom():
print(f"hello_tom {hello_tom.__name__=}")
def main():
hello_tom()
pass
if __name__ == "__main__":
main()
上面代码输出为:
in hello
start hello
hello_tom hello_tom.__name__='hello_tom'
end hello
多装饰器
import time
from functools import wraps
def timer(fn):
@wraps(fn)
def wrapper(*args,**kwargs):
print(f"start {time.perf_counter()}")
result = fn(*args, **kwargs)
print(f"end {time.perf_counter()}")
return result
print("in timer")
return wrapper
def hello(fn):
@wraps(fn)
def wrapper(*args,**kwargs):
print("start hello")
result = fn(*args,**kwargs)
print("end hello")
return result
print("in hello")
return wrapper
@timer
@hello
def hello_tom():
print(f"hello_tom {hello_tom.__name__=}")
def main():
hello_tom()
pass
if __name__ == "__main__":
main()
上面输出为
in hello
in timer
start 24362.822654945
start hello
hello_tom hello_tom.__name__='hello_tom'
end hello
end 24362.822663659
可以看出如果多个装饰器修饰一个函数,装饰器的执行顺序是从内向外依次执行的也就是先执行hello再执行timer
带参装饰器
from functools import wraps
def hello(name:str):
def decorator(fn):
@wraps(fn)
def wrapper(*args,**kwargs):
print("start hello")
result = fn(*args,**kwargs)
print("end hello")
return result
print(f"in decorator {name=}")
return wrapper
print(f"in hello {name=}")
return decorator
@hello("jerry")
def hello_tom():
print(f"hello_tom {hello_tom.__name__=}")
def main():
hello_tom()
pass
if __name__ == "__main__":
main()
输出为
in hello name='jerry'
in decorator name='jerry'
start hello
hello_tom hello_tom.__name__='hello_tom'
end hello
可以看出参数jerry已经传入装饰器了,带参数装饰器的实现通常涉及三层嵌套函数:
1.最外层函数负责接收装饰器参数。
2.中间层函数是真正的装饰器逻辑,它根据外层函数接收到的参数做一些跟参数相关的操作。
3.最内层的 wrapper 函数则是被返回并替换原函数的那部分,负责实际增强功能的执行。
类中的函数装饰器
Python要求装饰器必须是一个可被调用对象,而我们知道Python的类中有一个特殊的方法__call__,当实例对象像函数那样被调用时会执行__call__函数,这个特性使得实例可以表现得像一个函数。
from functools import wraps
class MyDecorator:
def __call__(self, fn):
@wraps(fn)
def wrapper(*args,**kwargs):
print("start wrapper")
result = fn(*args,**kwargs)
print("end wrapper")
return result
print(f"in __call__ {self}")
return wrapper
@MyDecorator()
def hello_tom():
print(f"{hello_tom.__name__=}")
def main():
hello_tom()
pass
if __name__ == "__main__":
main()
输出为:
in __call__ <__main__.MyDecorator object at 0x7f2d8b7f98b0>
start wrapper
hello_tom.__name__='hello_tom'
end wrapper
通过上面输出可以看出在类中实现一个函数装饰器会先创建一个对象,由于这个类实现了__call__函数使得这个对象有了类似函数的特性。
我们还可以在类的构造时传入一些参数如下:
class MyDecorator:
def __init__(self,name:str):
self.name=name
def __call__(self, fn):
@wraps(fn)
def wrapper(*args,**kwargs):
print("start wrapper")
print(f"{self.name=}")
result = fn(*args,**kwargs)
print("end wrapper")
return result
print(f"in __call__ {self}")
return wrapper
@MyDecorator("Jerry")
def hello_tom():
print(f"{hello_tom.__name__=}")
def main():
hello_tom()
pass
if __name__ == "__main__":
main()
输出为:
in __call__ <__main__.MyDecorator object at 0x7f2f4ac61bb0>
start wrapper
self.name='Jerry'
hello_tom.__name__='hello_tom'
end wrapper
当然装饰器也可以装饰类中的函数:
from functools import wraps
class MyDecorator:
def __init__(self,name:str):
self.name=name
def __call__(self, fn):
@wraps(fn)
def wrapper(*args,**kwargs):
print("start wrapper")
print(f"{self.name=}")
result = fn(*args,**kwargs)
print("end wrapper")
return result
print(f"in __call__ {self}")
return wrapper
class MyClass:
@MyDecorator("Jerry")
def hello_tom(self):
print(f"{self}")
def main():
m = MyClass()
m.hello_tom()
pass
if __name__ == "__main__":
main()
输出为
in __call__ <__main__.MyDecorator object at 0x7f1f9a861bb0>
start wrapper
self.name='Jerry'
<__main__.MyClass object at 0x7f1f9a861df0>
end wrapper
用于类的装饰器
Python中的装饰器不仅可以应用于函数还可以应用于类。装饰器用于在类实例化或类方法调用时自动执行一些额外的逻辑。装饰类的主要目的是在类的创建过程中添加功能
def class_decorator(c):
print(f"{c.__name__}")
fun = getattr(c,"fun",None)
if fun:
def decorator_fun(self,*args,**kwargs):
print("start decorator")
result = fun(self,*args,**kwargs)
print("start end")
return result
setattr(c,"fun",decorator_fun)
else:
print("not found")
return c
@class_decorator
class MyClass:
def __init__(self):
self.msg="hello"
def fun(self):
print(f"{self.msg=}")
def main():
m = MyClass()
m.fun()
pass
if __name__ == "__main__":
main()
输出为:
MyClass
start decorator
self.msg='hello'
start end
其实仔细分析上面代码会发现装饰类的装饰器本质是找到类中的方法名字进行替换,替换成自己实现的方法,在自己实现的方法中在调用原来类中的方法,这样可以在原来方法前后随意增加自己想要的东西。
下面使用类装饰器实现单例
def singleton(c):
instances={}
def get_instance(*args,**kwargs):
if c not in instances:
instances[c]=c(*args,**kwargs)
return instances[c]
return get_instance
@singleton
class MyClass:
pass
@singleton
class MyClass2:
pass
def main():
m = MyClass()
n = MyClass()
print(f"{id(m)} {id(n)}")
p = MyClass2()
q = MyClass2()
print(f"{id(p)} {id(q)}")
pass
if __name__ == "__main__":
main()
输出为:
140718111038480 140718111038480
140718111038528 140718111038528
可以非常容易的看出就是用一个字典的key存储了类名value里面存储了对象,每次创建对象时会先去字典中查找这个类有没有创建过对象,如果有对象直接将之前创建过的对象给出,如果之前没有创建过这个对象那么就会去创建一个新的存放到字典中。