Python装饰器

装饰器介绍

装饰器其实就是Python的一个特殊函数,它接收一个函数作为参数,然后返回另一个函数,Python中一般使用@语法糖来使用,它的作用就是,能在不修改原始函数的基础上为函数增加新的功能。关于装饰器的主要内容,涉及以下几个方面:

  • 如何使用装饰器,装饰器的好处
  • 带参数的装饰器
  • 多个装饰器的使用流程
  • 类装饰器

以下内容将围绕这四个点进行展开,其中首先讲解如何使用装饰器和装饰器的好处

如何使用装饰器,装饰器的好处

首先我们先任意定义一个函数,可以自定义也可以参考我的函数

python 复制代码
def Add(a:float, b:float):
    return a + b

我这里定义了一个Add函数,函数接收两个参数,分别是a和b,然后返回这两个数字的相加之和,这时候突然有了一个新的需求,要求为这个函数添加一个日志,获取函数在什么时候执行的,执行时候的参数是什么,函数的结果又是什么。

为了实现这个需求我们可以这么写,在函数中添加一点内容。

python 复制代码
from datetime import datetime
def Add(a:float, b:float):
    now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    c =  a + b
    print(f"[{now}] Add({a}, {b}) => {c}")
    return c
d = Add(5, 6)
print(d)

上面这段代码的运行结果如下

2024-06-01 04:51:04\] Add(5, 6) =\> 11 11

可以看到日志被成功的打印出来了,但是这样涉及到了编程中的一个概念,就是侵入式编程,也就是通过改变原始函数的执行内容来实现功能添加和修改,但是这里我们要注意到一个问题,就是日志并不是函数的核心内容,属于函数的附加功能,如果每次新增一个附加功能,就去修改一遍函数,那么函数将越来越混乱,严重影响函数的可读性。那么如何改变这一状况呢,很简单,设计一个日志函数,将找个函数加入到日志函数中运行即可。

python 复制代码
from datetime import datetime
def log(func):
    def wrapper(*args, **kwargs):
        now = datetime.now().strftime("%y-%m-%d %H:%M:%S")
        result = func(*args, **kwargs)
        kwargs_str = ','.join([f"{key} = {value}" for key, value in kwargs.items()])
        args_str = ','.join([str(x) for x in args])
        if not args_str and not kwargs_str:
            print(f"[{now}]{func.__name__}() => {result}")
        elif args_str and not kwargs_str:
            print(f"[{now}]{func.__name__}({args_str}) => {result}")
        elif not args_str and kwargs_str:
            print(f"[{now}]{func.__name__}({kwargs_str}) => {result}")
        else:
            print(f"[{now}]{func.__name__}({args_str}, {kwargs_str}) => {result}")
        return result
    return wrapper
def Add(a:float, b:float):
    return a + b
f = log(Add)
c = f(a=5, b=6)
print(c)

这段代码的执行结果跟上面是一样的,只不过是我们将日志打印和原始函数的执行分开来,而且这样还有一点好处就是,f = log(Add)这里的函数可以是任意的,对于任意函数的执行我们都可以获取到它的一个执行日志,而不用每个函数每个函数去修改。但是这样似乎还不是特别好,因为还要在f = log(Add)把函数重新创造一遍,看上去不美观,这里就用到了我们在一些代码中常见的@语法糖了,可以通过@语法糖浆代码做如下的更换。

python 复制代码
# 上面的代码不做任何处理
''' 
# 注释掉这一段
def Add(a:float, b:float):  
    return a + b  
f = log(Add)  
c = f(a=5, b=6)  
print(c)  
'''  
@log  
def Add(a: float, b:float):  
    return a + b  
c = Add(5, 6)  
print(c)

这段代码和上面的代码执行流程是一样的,通过这两段代码的一个运行来理解@的作用的就是

Add = log(Add) # 通过@实现了变量的重新赋值

@log装饰在某个函数上,就相当于 func = log(func)

装饰器的好处

通过上面的代码,可以看到,装饰器的核心优势就在于可以通过软编码在不修改原函数的基础上为原函数增加其他的功能,而且一个装饰器可以作用在多个函数上,避免了重复造轮子。

带参数的装饰器

带参数的装饰器使用的是函数工厂模式,也就是在装饰器函数的外部在增加一个函数,这个函数是一个闭包函数,它接收参数,返回值是闭包函数。

python 复制代码
from datetime import datetime
def Level(level='DEBUG'):
    def log(func):
        def wrapper(*args, **kwargs):
            now = datetime.now().strftime("%y-%m-%d %H:%M:%S")
            result = func(*args, **kwargs)
            kwargs_str = ','.join([f"{key} = {value}" for key, value in kwargs.items()])
            args_str = ','.join([str(x) for x in args])
            if not args_str and not kwargs_str:
                print(f"[{level}][{now}]{func.__name__}() => {result}")
            elif args_str and not kwargs_str:
                print(f"[{level}][{now}]{func.__name__}({args_str}) => {result}")
            elif not args_str and kwargs_str:
                print(f"[{level}][{now}]{func.__name__}({kwargs_str}) => {result}")
            else:
                print(f"[{level}][{now}]{func.__name__}({args_str}, {kwargs_str}) => {result}")
            return result
        return wrapper
    return log
@Level(level='INFO')
def Add(a: float, b:float):
    return  a + b
c = Add(5, 6)
print(c)

通过上面对@语法糖的理解,这里我们也可以理解为

Add = Level(level='INFO')(Add)

多个装饰器的使用流程

如果我们的函数要增加的附加功能很多,比如我需要在两个数字相加前判断传入的数据中是不是都是float类型,如果不是,将不会相加,而是直接返回-1

python 复制代码
from datetime import datetime
def Level(level='DEBUG'):
    def log(func):
        def wrapper(*args, **kwargs):
            now = datetime.now().strftime("%y-%m-%d %H:%M:%S")
            result = func(*args, **kwargs)
            kwargs_str = ','.join([f"{key} = {value}" for key, value in kwargs.items()])
            args_str = ','.join([str(x) for x in args])
            if not args_str and not kwargs_str:
                print(f"[{level}][{now}]{func.__name__}() => {result}")
            elif args_str and not kwargs_str:
                print(f"[{level}][{now}]{func.__name__}({args_str}) => {result}")
            elif not args_str and kwargs_str:
                print(f"[{level}][{now}]{func.__name__}({kwargs_str}) => {result}")
            else:
                print(f"[{level}][{now}]{func.__name__}({args_str}, {kwargs_str}) => {result}")
            return result
        return wrapper
    return log

def alert(func):
    def wrapper(*args, **kwargs):
        result = [arg for arg in args if not isinstance(arg, float)]
        result1 = [value for value in kwargs.values() if not(isinstance(value, float))]
        if result1 or result:
            return -1
        else:
            return func(*args, **kwargs)
    return wrapper

@Level(level='INFO')
@alert
def Add(a: float, b:float):
    return  a + b
c = Add(5, 6)
print(c)
d = Add(8.0, 9.0)
print(d)
e = Add('我', 9.0)
print(e)

当多个装饰器装饰一个函数时,将按从上到下的顺序来进行装饰,上面这段就相当于

Add = Level(level='INFO')(alert(Add))

类装饰器

类装饰器和普通装饰器的区别在于:普通装饰器通过返回函数来实现,类装饰器通过__call__特殊方法来实现。现在很多时候使用的装饰器是类装饰器,因为从理解上,类装饰器更便于理解

python 复制代码
from datetime import datetime
class Level:
    def __init__(self, level='DEBUG'):
        self.level = level
    def __call__(self, func):
        def wrapper(*args, **kwargs):
            now = datetime.now().strftime("%y-%m-%d %H:%M:%S")
            result = func(*args, **kwargs)
            kwargs_str = ','.join([f"{key} = {value}" for key, value in kwargs.items()])
            args_str = ','.join([str(x) for x in args])
            if not args_str and not kwargs_str:
                print(f"[{self.level}][{now}]{func.__name__}() => {result}")
            elif args_str and not kwargs_str:
                print(f"[{self.level}][{now}]{func.__name__}({args_str}) => {result}")
            elif not args_str and kwargs_str:
                print(f"[{self.level}][{now}]{func.__name__}({kwargs_str}) => {result}")
            else:
                print(f"[{self.level}][{now}]{func.__name__}({args_str}, {kwargs_str}) => {result}")
            return result
        return wrapper

def alert(func):
    def wrapper(*args, **kwargs):
        result = [arg for arg in args if not isinstance(arg, float)]
        result1 = [value for value in kwargs.values() if not(isinstance(value, float))]
        if result1 or result:
            return -1
        else:
            return func(*args, **kwargs)
    return wrapper

@Level(level='INFO')
def Add(a: float, b:float):
    return  a + b
Add(5, 7)

这里所演示的是一个带有参数的装饰器,也就是在使用装饰器是,必须带有(),否则将会产生异常,那么如何创建一个不需要带有()的装饰器呢,很简单,将__init__中的参数设置为func就可以。

python 复制代码
from datetime import datetime
class log:
    def __init__(self, func):
        self.func = func
    def __call__(self, *args, **kwargs):
        now = datetime.now().strftime("%y-%m-%d %H:%M:%S")
        result = self.func(*args, **kwargs)
        kwargs_str = ','.join([f"{key} = {value}" for key, value in kwargs.items()])
        args_str = ','.join([str(x) for x in args])
        if not args_str and not kwargs_str:
            print(f"[{now}]{self.func.__name__}() => {result}")
        elif args_str and not kwargs_str:
            print(f"[{now}]{self.func.__name__}({args_str}) => {result}")
        elif not args_str and kwargs_str:
            print(f"[{now}]{self.func.__name__}({kwargs_str}) => {result}")
        else:
            print(f"[{now}]{self.func.__name__}({args_str}, {kwargs_str}) => {result}")
        return result
def alert(func):
    def wrapper(*args, **kwargs):
        result = [arg for arg in args if not isinstance(arg, float)]
        result1 = [value for value in kwargs.values() if not(isinstance(value, float))]
        if result1 or result:
            return -1
        else:
            return func(*args, **kwargs)
    return wrapper

@log
def Add(a: float, b:float):
    return  a + b
Add(5, 7)
相关推荐
2301_77355362几秒前
mysql如何优化mysql在多核CPU下的性能_调整线程并发数
jvm·数据库·python
源码之家4 分钟前
计算机毕业设计:Python股票智能分析预测平台 Flask框架 数据分析 可视化 机器学习 随机森林 大数据(建议收藏)✅
python·机器学习·数据分析·django·flask·课程设计
a9511416424 分钟前
PHP如何批量处理AI请求_队列系统搭建【技巧】
jvm·数据库·python
sinat_383437365 分钟前
如何实现SQL简单数据的映射查询_使用CASE表达式替换
jvm·数据库·python
2401_835956815 分钟前
JavaScript 中实现基于分组的前端产品筛选功能
jvm·数据库·python
m0_746752307 分钟前
SQL中窗口函数的LIMIT限制逻辑_如何分页显示
jvm·数据库·python
Ice星空7 分钟前
使用 uv 进行 python 项目管理
开发语言·python·uv
m0_514520577 分钟前
Go语言怎么做自动补全_Go语言CLI自动补全教程【经典】
jvm·数据库·python
JaydenAI10 分钟前
[FastMCP设计、原理与应用-17]从服务器向客户端的反向通知
python·ai编程·ai agent·mcp·fastmcp
m0_7478545213 分钟前
php怎么使用PHP PM热重启_php如何零停机更新生产环境代码
jvm·数据库·python