浅析Python中装饰器的工作原理与应用

0 阅读6分钟

装饰器(Decorator)是 Python 中极具特色且强大的语法特性,它能在不修改原函数代码和调用方式的前提下,为函数添加额外功能。无论是日志记录、性能监控还是权限校验,装饰器都能优雅地解决这类 “横切关注点” 问题。本文将从基础原理到实战应用,全方位拆解 Python 装饰器,帮助新手彻底掌握这一核心技能。

一、装饰器的核心原理

要理解装饰器,首先要明确 Python 的两个核心特性:函数是一等对象闭包

1.1 函数是一等对象

在 Python 中,函数和整数、字符串一样,具备以下特性:

  • 可以赋值给变量
  • 可以作为参数传递给其他函数
  • 可以作为函数的返回值
  • 可以存储在数据结构(如列表、字典)中

示例代码:

python

运行

# 定义普通函数
def add(a, b):
    return a + b

# 1. 函数赋值给变量
func = add
print(func(1, 2))  # 输出:3

# 2. 函数作为参数传递
def call_func(func, x, y):
    return func(x, y)

print(call_func(add, 3, 4))  # 输出:7

# 3. 函数作为返回值
def wrapper():
    return add

print(wrapper()(5, 6))  # 输出:11

1.2 闭包:装饰器的基础

闭包是指嵌套函数引用了外部函数的变量,且外部函数返回嵌套函数。闭包能 “记住” 外部函数的环境,为装饰器提供了核心支撑。

示例代码:

python

运行

def outer(msg):
    # 嵌套函数inner引用了外部函数的msg变量
    def inner():
        print(f"传递的消息:{msg}")
    # 外部函数返回嵌套函数(不执行)
    return inner

# 创建闭包实例
func = outer("Hello Decorator")
# 执行闭包,仍能访问msg变量
func()  # 输出:传递的消息:Hello Decorator

1.3 装饰器的本质

装饰器本质上是一个接收函数作为参数,并返回新函数的闭包。它的核心逻辑是:

  1. 定义一个装饰器函数(外层函数),接收被装饰的函数作为参数;
  2. 定义一个包装函数(内层函数),在其中实现额外功能,并调用原函数;
  3. 外层函数返回包装函数。

最基础的装饰器示例:

python

运行

# 定义装饰器函数
def my_decorator(func):
    # 包装函数:添加额外功能
    def wrapper():
        print("===== 函数执行前的额外操作 =====")
        # 调用原函数
        func()
        print("===== 函数执行后的额外操作 =====")
    # 返回包装函数
    return wrapper

# 定义原函数
@my_decorator  # 语法糖,等价于 say_hello = my_decorator(say_hello)
def say_hello():
    print("Hello Python!")

# 调用被装饰后的函数
say_hello()

执行结果:

plaintext

===== 函数执行前的额外操作 =====
Hello Python!
===== 函数执行后的额外操作 =====

二、装饰器的进阶用法

2.1 装饰带参数的函数

包装函数需要接收原函数的参数,并传递给原函数:

python

运行

def logger(func):
    def wrapper(*args, **kwargs):  # 接收任意参数
        print(f"调用函数:{func.__name__},参数:{args}, {kwargs}")
        # 执行原函数并获取返回值
        result = func(*args, **kwargs)
        print(f"函数 {func.__name__} 执行完成,返回值:{result}")
        return result
    return wrapper

@logger
def calculate(a, b, op="+"):
    if op == "+":
        return a + b
    elif op == "*":
        return a * b
    else:
        return None

# 测试
calculate(10, 20)
calculate(5, 8, op="*")

执行结果:

plaintext

调用函数:calculate,参数:(10, 20), {'op': '+'}
函数 calculate 执行完成,返回值:30
调用函数:calculate,参数:(5, 8), {'op': '*'}
函数 calculate 执行完成,返回值:40

2.2 带参数的装饰器

如果需要给装饰器本身传递参数,需要再嵌套一层函数:

python

运行

# 带参数的装饰器:控制日志级别
def logger(level="info"):
    # 第一层:接收装饰器参数
    def decorator(func):
        # 第二层:接收被装饰函数
        def wrapper(*args, **kwargs):
            # 第三层:包装函数
            print(f"[{level.upper()}] 调用函数:{func.__name__}")
            result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator

# 使用带参数的装饰器
@logger(level="warning")
def delete_data(id):
    print(f"删除ID为 {id} 的数据")

@logger()  # 不传参数使用默认值
def query_data(name):
    print(f"查询名称为 {name} 的数据")

delete_data(1001)
query_data("Python")

执行结果:

plaintext

[WARNING] 调用函数:delete_data
删除ID为 1001 的数据
[INFO] 调用函数:query_data
查询名称为 Python 的数据

2.3 保留原函数的元信息

装饰器会替换原函数的__name____doc__等元信息,可通过functools.wraps修复:

python

运行

import functools

def my_decorator(func):
    @functools.wraps(func)  # 保留原函数元信息
    def wrapper(*args, **kwargs):
        print("添加额外功能")
        return func(*args, **kwargs)
    return wrapper

@my_decorator
def test():
    """test函数的文档字符串"""
    pass

# 未使用wraps时,test.__name__ 是 'wrapper'
print("函数名:", test.__name__)  # 输出:test
print("文档字符串:", test.__doc__)  # 输出:test函数的文档字符串

三、装饰器的实战应用场景

3.1 性能监控

统计函数执行耗时,定位性能瓶颈:

python

运行

import time
import functools

def timer(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"函数 {func.__name__} 执行耗时:{end - start:.4f} 秒")
        return result
    return wrapper

# 测试耗时装饰器
@timer
def slow_function():
    time.sleep(1.5)
    print("耗时函数执行完成")

slow_function()

3.2 权限校验

在接口调用前验证用户权限:

python

运行

def check_permission(role):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            # 模拟用户角色
            user_role = "admin"  # 实际场景从请求/会话中获取
            if user_role == role:
                return func(*args, **kwargs)
            else:
                raise PermissionError("无权限执行该操作")
        return wrapper
    return decorator

@check_permission(role="admin")
def delete_user(user_id):
    print(f"成功删除用户 {user_id}")

@check_permission(role="guest")
def view_user(user_id):
    print(f"查看用户 {user_id} 信息")

# 测试
delete_user(100)  # 正常执行
view_user(100)   # 抛出PermissionError

3.3 缓存结果

缓存函数计算结果,避免重复计算(适用于耗时的纯函数):

python

运行

def cache(func):
    # 用字典存储缓存结果
    cache_data = {}
    @functools.wraps(func)
    def wrapper(*args):
        # 以参数为键,判断是否已缓存
        if args in cache_data:
            print(f"使用缓存:{args}")
            return cache_data[args]
        # 未缓存则执行函数并存储结果
        result = func(*args)
        cache_data[args] = result
        return result
    return wrapper

@cache
def fibonacci(n):
    """计算斐波那契数(递归实现,性能差,适合缓存)"""
    if n <= 1:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

# 第一次调用:计算并缓存
print(fibonacci(10))
# 第二次调用:使用缓存
print(fibonacci(10))

四、类装饰器(扩展)

除了函数装饰器,Python 也支持类装饰器,核心是实现__call__方法:

python

运行

class CountCalls:
    def __init__(self, func):
        self.func = func
        self.count = 0  # 记录调用次数

    def __call__(self, *args, **kwargs):
        self.count += 1
        print(f"函数 {self.func.__name__} 已调用 {self.count} 次")
        return self.func(*args, **kwargs)

@CountCalls
def say_hi(name):
    print(f"Hi {name}!")

say_hi("Tom")
say_hi("Jerry")

执行结果:

plaintext

函数 say_hi 已调用 1 次
Hi Tom!
函数 say_hi 已调用 2 次
Hi Jerry!

总结

  1. 装饰器的核心是闭包,本质是接收函数并返回新函数的高阶函数,@语法糖简化了装饰器的调用;
  2. 装饰器可处理带参数的函数,也可自定义参数,使用functools.wraps能保留原函数元信息;
  3. 装饰器适用于日志记录、性能监控、权限校验、结果缓存等场景,是 Python “开闭原则” 的最佳实践。

掌握装饰器不仅能写出更优雅、可维护的代码,也是进阶 Python 开发的必经之路。建议从简单场景入手,逐步尝试自定义装饰器,加深对其原理的理解。