# Python 装饰器 在 Python 中,装饰器是一个非常强大的功能,可以在不修改函数代码的情况下,动态地修改函数/方法的行为。装饰器本质上是一个函数,它接受一个函数作为参数并返回一个新的函数。 应用场景:比如插入日志,性能测试,事务处理,缓存等等场景。 ## 案例切入 已知有一个函数 `func1()`,作用是输出一句话。现在我们想要给他增加额外的功能。但是为了保障已有功能的稳定,不能更改原函数。 ```python def func1(): print("in func1") # 新的需求,能够打印如下内容... # hello world # in func1 # hello python # 实现 def func2(func): def inner(): print("hello world") func() print("hello python") return inner func1 = func2(func1) func1() ``` ## 装饰器形成的过程 如果我想测试某个函数的执行时间 ```python import time def func1(): print('in func1') def timer(func): def inner(): start = time.time() func() print(time.time() - start) return inner func1 = timer(func1) # 将函数本身做为参数传递进去 func1() ``` 如果有很多个函数都需要测试它们的执行时间,每次都需要 `func1 = timer(func1)` 是比较麻烦的,而且不利于代码的可读性和后期维护。这里我们可以使用 python 中的一种特殊的语法结构**语法糖**,来更为简便的使用装饰器。 我们将上述代码修改如下: ```python import time def timer(func): def inner(): start = time.time() func() print(time.time() - start) return inner @timer """ 当我们在某个函数上方使用 @my_decorator 的时候,python 会自动将下面定义的函数做为参数传递给 my_decorator。 func1 = timer(func1) """ def func1(): time.sleep(1) print('in func1') func1() ``` ## 装饰带参数的函数 装饰一个带参数的函数与装饰一个不带参数的函数类似,但需要在装饰器中处理传递给被装饰函数的参数。 **示例:** ```python import time def timer(func): def inner(a): start = time.time() func(a) print(time.time() - start) return inner @timer """ func1 = timer(func1) """ def func1(a): time.sleep(1) print(a) func1('hello world') ``` ## 装饰带多个参数的函数 这里我们利用了函数里面的动态参数进行传参 ```python def my_decorator(func): def wrapper(*args, **kwargs): # 打印传入的参数 print(f"调用 {func.__name__} 函数,参数: {args}, 关键字参数: {kwargs}") # 调用原始函数并获取结果 result = func(*args, **kwargs) # 打印返回结果 print(f"{func.__name__} 函数返回: {result}") return result return wrapper @my_decorator def add(x, y): """返回两个数的和""" return x + y # 测试 result = add(5, 3) print(f"最终结果: {result}") ``` # wraps装饰器 回到我们最开始的案例 ```python import time def func1(): print('in func1') def timer(func): def inner(): start = time.time() func() print(time.time() - start) return inner func1 = timer(func1) # 将函数本身做为参数传递进去 func1() ``` 思考一个问题:这里虽然我们最后还是执行 `func1` 函数,但是这里的 `func1` 函数还是我们最初的`func1`函数吗? ...... 我们先来看一下最后的 `func1` 他的函数名 ```python import time def func1(): print('in func1') def timer(func): def inner(): start = time.time() func() print(time.time() - start) return inner func1 = timer(func1) # 将函数本身做为参数传递进去 print(func1.__name__) # 查看函数的名称 ``` ## 导入wraps装饰器 `wraps` 装饰器,用于帮助创建装饰器时保留被装饰函数的元数据(如名称、文档字符串等)。使用 `@wraps` 可以确保装饰后的函数看起来像原始函数,这样有助于调试和文档生成。 我们将上方的案例使用 wraps 装饰器装饰 ```python from functools import wraps import time def func1(): print('in func1') def timer(func): @wraps(func) def inner(): start = time.time() func() print(time.time() - start) return inner func1 = timer(func1) # 将函数本身做为参数传递进去 print(func1.__name__) # 查看函数的名称 ``` # 带参数的装饰器 带参数的装饰器允许你在装饰器中接受参数,从而增强装饰器的灵活性和功能性。实现带参数的装饰器通常需要使用嵌套函数。 我们将创建一个装饰器,它接受一个参数,用于指定**是否打印函数的执行时间**。 ```python import time from functools import wraps def timing_decorator(print_time=True): def decorator(func): @wraps(func) def wrapper(*args, **kwargs): start_time = time.time() # 记录开始时间 result = func(*args, **kwargs) # 调用原始函数 end_time = time.time() # 记录结束时间 if print_time: execution_time = end_time - start_time print(f"{func.__name__} 执行时间: {execution_time:.4f}秒") return result return wrapper return decorator @timing_decorator(print_time=True) """ add = timing_decorator(print_time=True)(add) """ def add(x, y): """返回两个数的和""" time.sleep(1) # 模拟耗时操作 return x + y @timing_decorator(print_time=False) def multiply(x, y): """返回两个数的积""" time.sleep(1) # 模拟耗时操作 return x * y # 测试 result_add = add(5, 3) print(f"加法结果: {result_add}") result_multiply = multiply(5, 3) print(f"乘法结果: {result_multiply}") ``` # 多个装饰器装饰一个函数 可以将多个装饰器应用于同一个函数。这种情况下,装饰器会按照从内到外的顺序依次应用。 ```python def wrapper1(func): def inner1(): print('第一个装饰器,在程序运行之前') func() print('第一个装饰器,在程序运行之后') return inner1 def wrapper2(func): def inner2(): print('第二个装饰器,在程序运行之前') func() print('第二个装饰器,在程序运行之后') return inner2 @wrapper1 @wrapper2 def f(): print('Hello') f() """ 执行过程分析: 1. f = wrapper2(f) -> func = f, return inner2 2. f = wrapper1(f) -> func = inner2, return inner1 3. f() = inner1() -> inner2() -> f() -> inner2() -> inner1() """ ``` ## 示例:多个装饰器 创建两个装饰器,一个用于打印函数的执行时间,另一个用于打印调用的参数。 ```python import time from functools import wraps # 装饰器 1:打印执行时间 def timing_decorator(func): @wraps(func) def wrapper(*args, **kwargs): start_time = time.time() result = func(*args, **kwargs) end_time = time.time() execution_time = end_time - start_time print(f"{func.__name__} 执行时间: {execution_time:.4f}秒") return result return wrapper # 装饰器 2:打印函数参数 def logging_decorator(func): @wraps(func) def wrapper(*args, **kwargs): print(f"调用 {func.__name__} 函数,参数: {args}, 关键字参数: {kwargs}") return func(*args, **kwargs) return wrapper @timing_decorator @logging_decorator def add(x, y): """返回两个数的和""" time.sleep(1) # 模拟耗时操作 return x + y # 测试 result = add(5, 3) print(f"加法结果: {result}") ``` # 装饰器的固定结构 ```python from functools import wraps def decorator_with_args(param): def actual_decorator(func): @wraps(func) def wrapper(*args, **kwargs): print(f"装饰器参数: {param}") return func(*args, **kwargs) return wrapper return actual_decorator @decorator_with_args("配置参数") def my_function(): pass ```