Files
Cloud-book/Python/Python面向函数/装饰器函数.md
2025-08-27 17:10:05 +08:00

8.0 KiB
Raw Blame History

Python 装饰器

在 Python 中,装饰器是一个非常强大的功能,可以在不修改函数代码的情况下,动态地修改函数/方法的行为。装饰器本质上是一个函数,它接受一个函数作为参数并返回一个新的函数。

应用场景:比如插入日志,性能测试,事务处理,缓存等等场景。

案例切入

已知有一个函数 func1(),作用是输出一句话。现在我们想要给他增加额外的功能。但是为了保障已有功能的稳定,不能更改原函数。

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()

装饰器形成的过程

如果我想测试某个函数的执行时间

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 中的一种特殊的语法结构语法糖,来更为简便的使用装饰器。

我们将上述代码修改如下:

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() 

装饰带参数的函数

装饰一个带参数的函数与装饰一个不带参数的函数类似,但需要在装饰器中处理传递给被装饰函数的参数。

示例:

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')

装饰带多个参数的函数

这里我们利用了函数里面的动态参数进行传参

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装饰器

回到我们最开始的案例

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 他的函数名

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 装饰器装饰

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__)   # 查看函数的名称

带参数的装饰器

带参数的装饰器允许你在装饰器中接受参数,从而增强装饰器的灵活性和功能性。实现带参数的装饰器通常需要使用嵌套函数。

我们将创建一个装饰器,它接受一个参数,用于指定是否打印函数的执行时间

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}")

多个装饰器装饰一个函数

可以将多个装饰器应用于同一个函数。这种情况下,装饰器会按照从内到外的顺序依次应用。

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()
"""

示例:多个装饰器

创建两个装饰器,一个用于打印函数的执行时间,另一个用于打印调用的参数。

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}")

装饰器的固定结构

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