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

相关推荐
美若黎明@6 分钟前
C# 路径操作
开发语言·c#
难以触及的高度33 分钟前
source ~/.bash_profile有什么用
开发语言·bash
骆晨学长44 分钟前
基于springboot学生健康管理系统的设计与实现
java·开发语言·spring boot·后端·spring
白总Server1 小时前
php语言基本语法
开发语言·ide·后端·golang·rust·github·php
小林熬夜学编程1 小时前
【Linux系统编程】第二十弹---进程优先级 && 命令行参数 && 环境变量
linux·运维·服务器·c语言·开发语言·算法
A懿轩A1 小时前
MySQL SQL多表查询语句各种连接
java·开发语言·数据库·sql·mysql·mybatis
网安詹姆斯1 小时前
网络安全(黑客技术)2024年三个月自学计划
网络·数据结构·python·mysql·安全·web安全·github
宇宙第一小趴菜1 小时前
中秋节特别游戏:给玉兔投喂月饼
python·游戏·pygame·中秋节
zls3653651 小时前
C# WPF中实现深拷贝的五种方式
开发语言·c#
六点半8881 小时前
【C/C++】涉及string类的经典OJ编程题
c语言·开发语言·c++·算法