Python高级技巧:装饰器全面指南,从基础到高级应用

Python装饰器全面指南:从基础到高级应用

一、理解装饰器:概念与原理

1.1 前置知识:闭包函数

在深入理解装饰器之前,必须先掌握闭包函数的概念。闭包是装饰器的基石,理解了闭包,装饰器的工作原理就一目了然。

嵌套函数:在一个函数内部定义另一个函数,这就是嵌套函数。外层的称为外函数,内层的称为内函数。

闭包函数:当内函数使用了外函数的局部变量,并且外函数返回了这个内函数,就形成了闭包。

python 复制代码
def nth_power(exponent):  # 外函数
    def exponent_of(base):  # 内函数(闭包函数)
        return base ** exponent  # 使用了外函数的局部变量exponent
    return exponent_of  # 返回内函数

# 创建闭包
square = nth_power(2)  # exponent=2
cube = nth_power(3)    # exponent=3

print(square(2))  # 输出: 4 (2²)
print(cube(2))    # 输出: 8 (2³)

# 验证闭包特性
print(square.__closure__[0].cell_contents)  # 输出: 2

闭包的关键特性是保存了外部函数的局部变量,即使外部函数已经执行完毕,闭包函数仍然可以访问这些变量。

1.2 装饰器的本质与定义

装饰器本质上是一种特殊的嵌套函数 ,它接收一个函数作为参数(被装饰的函数),并返回一个新的函数(装饰后的函数)。装饰器的最大价值在于不修改原函数代码的情况下,为函数添加新功能。

python 复制代码
# 一个简单的装饰器示例
def decorator(func):  # 接收一个函数作为参数
    def wrapper():    # 定义一个新函数
        print("Before function call")
        func()        # 调用原函数
        print("After function call")
    return wrapper    # 返回新函数

def say_hello():
    print("Hello!")

# 手动装饰
decorated_hello = decorator(say_hello)
decorated_hello()
# 输出:
# Before function call
# Hello!
# After function call

1.3 装饰器的基本工作机制

装饰器的工作原理基于Python的函数是一等公民的特性:函数可以作为参数传递,也可以作为返回值。

python 复制代码
def simple_decorator(func):
    def inner():
        print("装饰器开始执行")
        func()
        print("装饰器执行结束")
    return inner

def original_function():
    print("原始函数执行")

# 装饰过程
original_function = simple_decorator(original_function)

# 此时original_function实际上是inner函数
original_function()
# 输出:
# 装饰器开始执行
# 原始函数执行
# 装饰器执行结束

二、装饰器的基本语法与应用

2.1 装饰器的基本用法

Python提供了简洁的装饰器语法糖 ,使用@符号简化装饰器的应用:

python 复制代码
def simple_decorator(func):
    def inner():
        print("Before execution")
        func()
        print("After execution")
    return inner

@simple_decorator  # 语法糖,等价于:func = simple_decorator(func)
def func():
    print("Function body")

func()
# 输出:
# Before execution
# Function body
# After execution

2.2 装饰带参数函数

当被装饰的函数有参数时,装饰器内部需要相应处理这些参数:

python 复制代码
import time

def timer(func):
    """计算函数执行时间的装饰器"""
    def wrapper(*args, **kwargs):  # 接收任意参数
        start_time = time.time()
        result = func(*args, **kwargs)  # 传递参数给原函数
        end_time = time.time()
        print(f"{func.__name__} 执行时间: {end_time - start_time:.6f}秒")
        return result
    return wrapper

@timer
def sleep_function(seconds):
    """模拟耗时操作"""
    time.sleep(seconds)
    return f"睡了{seconds}秒"

@timer
def add_numbers(a, b):
    """加法函数"""
    return a + b

print(sleep_function(1))  # 输出执行时间并返回结果
print(add_numbers(3, 5))  # 输出执行时间并返回结果

2.3 带参数的装饰器

装饰器本身也可以接收参数,这需要在装饰器外部再包裹一层函数:

python 复制代码
def repeat(times):
    """重复执行函数的装饰器工厂"""
    def decorator(func):
        def wrapper(*args, **kwargs):
            results = []
            for i in range(times):
                print(f"第{i+1}次执行:")
                result = func(*args, **kwargs)
                results.append(result)
            return results
        return wrapper
    return decorator

@repeat(times=3)  # 装饰器带参数
def greet(name):
    return f"Hello, {name}!"

print(greet("Alice"))
# 输出:
# 第1次执行:
# 第2次执行:
# 第3次执行:
# ['Hello, Alice!', 'Hello, Alice!', 'Hello, Alice!']

# 等价写法:
# greet = repeat(times=3)(greet)

2.4 装饰器嵌套与执行顺序

一个函数可以被多个装饰器装饰,装饰器的执行顺序是从下往上(从最靠近函数的装饰器开始):

python 复制代码
def decorator_one(func):
    def wrapper(*args, **kwargs):
        print("装饰器一:开始")
        result = func(*args, **kwargs)
        print("装饰器一:结束")
        return result
    return wrapper

def decorator_two(func):
    def wrapper(*args, **kwargs):
        print("装饰器二:开始")
        result = func(*args, **kwargs)
        print("装饰器二:结束")
        return result
    return wrapper

@decorator_one
@decorator_two
def my_function():
    print("原始函数执行")

my_function()
# 输出:
# 装饰器一:开始
# 装饰器二:开始
# 原始函数执行
# 装饰器二:结束
# 装饰器一:结束

# 等价于:
# my_function = decorator_one(decorator_two(my_function))

三、高级装饰器模式

3.1 类装饰器

除了函数,类也可以作为装饰器。类装饰器通过实现__call__方法,使实例可以像函数一样被调用:

python 复制代码
class TimerDecorator:
    """类装饰器:计时器"""
    
    def __init__(self, func):
        self.func = func
        self.count = 0
    
    def __call__(self, *args, **kwargs):
        import time
        start_time = time.time()
        result = self.func(*args, **kwargs)
        end_time = time.time()
        self.count += 1
        
        print(f"{self.func.__name__} 第{self.count}次调用")
        print(f"执行时间: {end_time - start_time:.6f}秒")
        return result

@TimerDecorator
def expensive_operation(n):
    """模拟耗时计算"""
    result = sum(i * i for i in range(n))
    return result

print(expensive_operation(10000))
print(expensive_operation(20000))  # 第二次调用会显示不同的计数

3.2 保留函数元信息

使用装饰器后,原函数的元信息(如函数名、文档字符串)会被包装函数覆盖。使用functools.wraps可以解决这个问题:

python 复制代码
import functools

def debug_decorator(func):
    """保留元信息的调试装饰器"""
    @functools.wraps(func)  # 保留原函数信息
    def wrapper(*args, **kwargs):
        print(f"调用函数: {func.__name__}")
        print(f"参数: args={args}, kwargs={kwargs}")
        result = func(*args, **kwargs)
        print(f"返回值: {result}")
        return result
    return wrapper

@debug_decorator
def calculate_sum(a, b):
    """计算两个数的和"""
    return a + b

print(calculate_sum(3, 4))
print(f"函数名: {calculate_sum.__name__}")  # 输出: calculate_sum
print(f"文档字符串: {calculate_sum.__doc__}")  # 输出: 计算两个数的和

3.3 可选参数装饰器

创建既支持带参数也支持不带参数的装饰器:

python 复制代码
import functools

def optional_decorator(func=None, *, prefix="DEBUG"):
    """可选参数的装饰器"""
    if func is None:
        # 装饰器被带参数调用
        return functools.partial(optional_decorator, prefix=prefix)
    
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print(f"[{prefix}] 调用: {func.__name__}")
        return func(*args, **kwargs)
    return wrapper

# 不带参数使用
@optional_decorator
def function_one():
    print("函数一执行")

# 带参数使用
@optional_decorator(prefix="INFO")
def function_two():
    print("函数二执行")

function_one()  # 输出: [DEBUG] 调用: function_one
function_two()  # 输出: [INFO] 调用: function_two

四、装饰器在实际项目中的应用

4.1 日志记录装饰器

记录函数调用信息,便于调试和监控:

python 复制代码
import logging
import functools

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)

def log_decorator(logger=None):
    """日志记录装饰器"""
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            nonlocal logger
            if logger is None:
                logger = logging.getLogger(func.__module__)
            
            logger.info(f"开始执行: {func.__name__}")
            logger.debug(f"参数: {args}, {kwargs}")
            
            try:
                result = func(*args, **kwargs)
                logger.info(f"执行成功: {func.__name__}")
                logger.debug(f"返回值: {result}")
                return result
            except Exception as e:
                logger.error(f"执行失败: {func.__name__}, 错误: {e}")
                raise
        return wrapper
    return decorator

@log_decorator()
def process_data(data):
    """处理数据函数"""
    return [item * 2 for item in data]

result = process_data([1, 2, 3, 4, 5])
print(f"处理结果: {result}")

4.2 权限验证装饰器

在Web应用或API中控制访问权限:

python 复制代码
class User:
    """用户类"""
    def __init__(self, username, role):
        self.username = username
        self.role = role

def require_role(allowed_roles):
    """角色权限验证装饰器"""
    def decorator(func):
        def wrapper(user, *args, **kwargs):
            if user.role not in allowed_roles:
                raise PermissionError(
                    f"用户 {user.username} 没有权限执行此操作。"
                    f"需要角色: {allowed_roles}, 当前角色: {user.role}"
                )
            return func(user, *args, **kwargs)
        return wrapper
    return decorator

# 定义权限
ADMIN = 'admin'
EDITOR = 'editor'
VIEWER = 'viewer'

@require_role([ADMIN, EDITOR])
def edit_document(user, document_id):
    """编辑文档"""
    return f"用户 {user.username} 正在编辑文档 {document_id}"

@require_role([ADMIN])
def delete_document(user, document_id):
    """删除文档"""
    return f"用户 {user.username} 正在删除文档 {document_id}"

# 测试
admin = User("admin_user", ADMIN)
editor = User("editor_user", EDITOR)
viewer = User("viewer_user", VIEWER)

print(edit_document(editor, 123))  # 正常执行
print(edit_document(admin, 456))   # 正常执行
try:
    print(edit_document(viewer, 789))  # 抛出权限错误
except PermissionError as e:
    print(f"权限错误: {e}")
相关推荐
Kurbaneli2 小时前
Python的起源与发展
python
540_5402 小时前
ADVANCE Day26
人工智能·python·机器学习
南_山无梅落2 小时前
11.Python 常用数据类型「增删改查」操作总结表格
python
wenxiaohai1232 小时前
在anaconda中安装cuda-pytorch
人工智能·pytorch·python·anaconda
Dingdangr2 小时前
基于Python的火焰识别系统设计与实现(含论文、开题报告及答辩PPT)
java·python·测试工具·安全
吴佳浩 Alben2 小时前
Python入门指南(五) - 为什么选择 FastAPI?
开发语言·python·fastapi
山土成旧客2 小时前
【Python学习打卡-Day25】从程序崩溃到优雅处理:掌握Python的异常处理艺术
人工智能·python·学习
给你一页白纸2 小时前
Pytest 测试用例自动生成:接口自动化进阶实践
python·pytest·接口自动化
小鸡吃米…2 小时前
Python - 发送电子邮件
开发语言·python