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)
相关推荐
XMYX-027 分钟前
Python 操作 Elasticsearch 全指南:从连接到数据查询与处理
python·elasticsearch·jenkins
正义的彬彬侠31 分钟前
sklearn.datasets中make_classification函数
人工智能·python·机器学习·分类·sklearn
belldeep33 分钟前
python:用 sklearn 转换器处理数据
python·机器学习·sklearn
安静的_显眼包O_o34 分钟前
from sklearn.preprocessing import Imputer.处理缺失数据的工具
人工智能·python·sklearn
安静的_显眼包O_o39 分钟前
from sklearn.feature_selection import VarianceThreshold.移除低方差的特征来减少数据集中的特征数量
人工智能·python·sklearn
_可乐无糖1 小时前
pytest中的断言
python·pytest
Wils0nEdwards1 小时前
Leetcode 整数转罗马数字
linux·python·leetcode
云空1 小时前
《InsCode AI IDE:编程新时代的引领者》
java·javascript·c++·ide·人工智能·python·php
zqzgng1 小时前
Python 数据可视化pilot
开发语言·python·信息可视化
Dr_eamboat1 小时前
【Java】枚举类映射
java·开发语言·python