python 装饰器

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里面存储了对象,每次创建对象时会先去字典中查找这个类有没有创建过对象,如果有对象直接将之前创建过的对象给出,如果之前没有创建过这个对象那么就会去创建一个新的存放到字典中。

相关推荐
Java后端的Ai之路9 小时前
【Python 教程15】-Python和Web
python
Coder个人博客10 小时前
Linux6.19-ARM64 mm mmu子模块深入分析
大数据·linux·车载系统·系统架构·系统安全·鸿蒙系统
冬奇Lab11 小时前
一天一个开源项目(第15篇):MapToPoster - 用代码将城市地图转换为精美的海报设计
python·开源
灰子学技术12 小时前
go response.Body.close()导致连接异常处理
开发语言·后端·golang
Doro再努力12 小时前
Vim 快速上手实操手册:从入门到生产环境实战
linux·编辑器·vim
wypywyp13 小时前
8. ubuntu 虚拟机 linux 服务器 TCP/IP 概念辨析
linux·服务器·ubuntu
二十雨辰13 小时前
[python]-AI大模型
开发语言·人工智能·python
Doro再努力13 小时前
【Linux操作系统10】Makefile深度解析:从依赖推导到有效编译
android·linux·运维·服务器·编辑器·vim
senijusene13 小时前
Linux软件编程:IO编程,标准IO(1)
linux·运维·服务器
Yvonne爱编码13 小时前
JAVA数据结构 DAY6-栈和队列
java·开发语言·数据结构·python