装饰器入门:Python装饰器如何给函数“加功能”?

在Python中,函数是“一等公民”——它可以被当作参数传递、赋值给变量,甚至作为返回值。装饰器正是利用了这一特性,让我们能在不修改原函数代码的前提下,给函数“动态添加功能”。这就像给礼物盒套上一个漂亮的包装纸,既保留了礼物本身,又增添了新的外观。

一、为什么需要装饰器?

假设我们要给多个函数添加“打印执行日志”的功能。如果直接修改每个函数,代码会变得重复且难以维护。例如:

# 原始函数1
def add(a, b):
    return a + b

# 原始函数2
def subtract(a, b):
    return a - b

如果想给它们加日志,最直接的方式是手动修改每个函数:

# 加了日志的add函数
def add(a, b):
    print(f"函数add开始执行,参数:a={a}, b={b}")
    result = a + b
    print(f"函数add执行结束,结果:{result}")
    return result

# 加了日志的subtract函数(重复代码)
def subtract(a, b):
    print(f"函数subtract开始执行,参数:a={a}, b={b}")
    result = a - b
    print(f"函数subtract执行结束,结果:{result}")
    return result

这样做显然不优雅。如果有100个函数,就要重复写100遍日志代码。装饰器的出现,就是为了解决这种“重复造轮子”的问题

二、装饰器的基本原理

装饰器本质是一个函数,它接收一个函数作为参数,并返回一个新函数(这个新函数“包装”了原函数,添加了额外功能)。

1. 最简单的装饰器

我们先定义一个“日志装饰器”,它能给任何函数添加“开始/结束执行”的日志:

def log_decorator(func):  # 接收原函数func
    def wrapper(*args, **kwargs):  # 内部函数(包装器)
        # 1. 添加功能:打印开始日志
        print(f"函数 {func.__name__} 开始执行,参数:{args}, {kwargs}")
        # 2. 调用原函数
        result = func(*args, **kwargs)
        # 3. 添加功能:打印结束日志
        print(f"函数 {func.__name__} 执行结束,结果:{result}")
        return result
    return wrapper  # 返回包装后的函数

2. 使用装饰器

@ 语法糖(装饰器语法)把原函数“包裹”起来:

@log_decorator  # 相当于:add = log_decorator(add)
def add(a, b):
    return a + b

@log_decorator  # 同理:subtract = log_decorator(subtract)
def subtract(a, b):
    return a - b

3. 调用测试

现在调用 add(1, 2) 时,实际执行的是 wrapper 函数,会自动打印日志:

add(1, 2)
# 输出:
# 函数 add 开始执行,参数:(1, 2), {}
# 函数 add 执行结束,结果:3

三、装饰器的核心细节

1. *args**kwargs

*args 接收位置参数(如 add(1, 2) 中的 1, 2),**kwargs 接收关键字参数(如 add(a=1, b=2) 中的 a=1, b=2)。这确保装饰器能适配任何参数的函数。

2. 保留原函数信息

如果直接用上述装饰器,add.__name__ 会变成 wrapper(因为 add 被替换成了 wrapper)。这时可以用 functools.wraps 保留原函数的元信息:

from functools import wraps

def log_decorator(func):
    @wraps(func)  # 保留原函数信息
    def wrapper(*args, **kwargs):
        print(f"函数 {func.__name__} 开始执行")
        result = func(*args, **kwargs)
        print(f"函数 {func.__name__} 执行结束")
        return result
    return wrapper

# 现在查看函数名
print(add.__name__)  # 输出:add(而非 wrapper)

四、带参数的装饰器

如果需要给装饰器传递参数(如不同的日志前缀),可以嵌套一层函数:

def log_decorator(prefix="【日志】"):  # 外层函数:接收装饰器参数
    def decorator(func):  # 内层函数:接收原函数
        @wraps(func)
        def wrapper(*args, **kwargs):
            print(f"{prefix} 函数 {func.__name__} 开始执行")
            result = func(*args, **kwargs)
            print(f"{prefix} 函数 {func.__name__} 执行结束")
            return result
        return wrapper
    return decorator  # 返回内层装饰器

# 使用带参数的装饰器
@log_decorator(prefix="【调试】")
def multiply(a, b):
    return a * b

multiply(3, 4)
# 输出:
# 【调试】 函数 multiply 开始执行
# 【调试】 函数 multiply 执行结束

五、装饰器的应用场景

装饰器非常灵活,常见场景包括:
- 日志记录:记录函数调用时间、参数、返回值。
- 性能测试:统计函数执行耗时(如 @timer_decorator)。
- 权限验证:调用函数前检查用户权限(如 @check_permission)。
- 缓存:缓存函数结果,避免重复计算(如 @cache_decorator)。

六、多个装饰器的执行顺序

如果多个装饰器作用于同一函数,执行顺序是从下往上(靠近函数的装饰器先执行)。例如:

@decorator2
@decorator1
def func():
    pass

执行顺序是:decorator2decorator1funcdecorator1decorator2

总结

装饰器是Python的“魔法工具”,通过函数嵌套和闭包思想,让我们能优雅地给函数添加功能。核心要点:
1. 装饰器是接收函数的函数,返回新函数。
2. @ 语法糖简化了装饰器的使用。
3. *args**kwargs 处理任意参数,functools.wraps 保留原函数信息。
4. 带参数的装饰器需嵌套两层函数。

从简单的日志到复杂的权限验证,装饰器让代码更简洁、可维护。现在,你可以尝试给一个自己的函数加上日志装饰器,体验它的便捷了!

Xiaoye