《Python 装饰器设计模式:从零实现日志、缓存、权限校验等功能》详细解释

发布于:2025-07-20 ⋅ 阅读:(11) ⋅ 点赞:(0)

1.装饰器是什么?用生活例子秒懂

        想象你有一个普通的手机(就像一个基础函数):它能打电话、发消息(核心功能)。现在你想给它加个手机壳(防摔)、贴个膜(防刮)、挂个挂件(好看)—— 这些 "额外功能" 不会改变手机本身的打电话功能,但让它更实用了。

        在 Python 中,装饰器就是给函数 "加装备" 的工具:它能在不修改函数原有代码的前提下,给函数添加新功能,例如:

  1. 日志记录:可以使用装饰器来记录函数的输入、输出或执行时间。

  2. 认证和授权:装饰器可以用于检查用户是否有权限执行特定操作。

  3. 缓存:装饰器可以缓存函数的结果,从而提高执行效率。

  4. 参数验证:可以使用装饰器来验证函数的输入参数是否符合预期。

  5. 代码注入:装饰器可以在函数的执行前后注入额外的代码。

        举个最简单的例子:有一个打印 "你好" 的函数,我们用装饰器给它加个 "开场白" 和 "结束语",效果如下:

# 装饰器:给函数加前后提示
def add_tips(func):
    def wrapper():
        print("===== 开始 =====")  # 新增功能
        func()  # 原函数功能
        print("===== 结束 =====")  # 新增功能
    return wrapper

# 用装饰器"装饰"函数
@add_tips
def say_hello():
    print("你好,装饰器!")

# 调用函数
say_hello()

'''
===== 开始 =====
你好,装饰器!
===== 结束 =====

'''

        原函数say_hello的代码没改,但多了前后提示 

1.1 装饰器的 "底层逻辑":从闭包到语法糖

        装饰器的本质是闭包的语法简化。先看一个不用@符号的 "手动装饰" 过程,帮你理解原理:

# 1. 定义一个简单函数
def say_hi():
    print("hi~")

# 2. 定义装饰器(闭包结构)
def my_decorator(func):
    def wrapper():
        print("装饰开始")
        func()
        print("装饰结束")
    return wrapper

# 3. 手动用装饰器处理函数(等价于@my_decorator)
say_hi = my_decorator(say_hi)

# 4. 调用"装饰后"的函数
say_hi()  # 输出:装饰开始 → hi~ → 装饰结束

核心结论:

  • @装饰器名 是 Python 的 "语法糖"(简化写法),等价于 函数名 = 装饰器(函数名)
  • 装饰器的作用是 "替换" 原函数:调用原函数名时,实际执行的是装饰器里的wrapper函数。

 

1.2基础装饰器:给函数加固定功能

场景:给所有打印函数加 "分隔线",让输出更清晰。

def add_separator(func):
    def wrapper():
        print("-" * 20)  # 新增:打印分隔线
        func()  # 执行原函数
        print("-" * 20)  # 新增:打印分隔线
    return wrapper

@add_separator
def print_greeting():
    print("欢迎使用本程序")

@add_separator
def print_farewell():
    print("感谢使用,再见")

print_greeting()
# 输出:
# --------------------
# 欢迎使用本程序
# --------------------

print_farewell()
# 输出:
# --------------------
# 感谢使用,再见
# --------------------

 解析add_separator装饰器像一个 "模板",可以复用给多个函数,避免重复写分隔线代码。

1.3带参数的装饰器:让功能更灵活

场景:希望控制函数重复执行的次数(比如重复打印 3 次、5 次)。

def repeat(times):  # 外层:接收参数(重复次数)
    def decorator(func):  # 中层:接收函数
        def wrapper(*args, **kwargs):  # 内层:执行逻辑
            for _ in range(times):
                func(*args, **kwargs)  # 带参数调用原函数
        return wrapper
    return decorator

@repeat(times=3)  # 重复3次
def say(name):
    print(f"你好,{name}!")

say("小明")  # 输出:你好,小明!→ 你好,小明!→ 你好,小明!

 关键细节:

  • 带参数的装饰器需要三层函数:外层收参数、中层收函数、内层写执行逻辑。
  • *args**kwargs用于接收原函数的参数(比如name),确保装饰器适配任何参数的函数。

1.3 装饰器链:多个装饰器一起用

场景:先把文本转大写,再在末尾加感叹号(组合两个功能)。

# 装饰器1:转大写
def to_uppercase(func):
    def wrapper(name):
        result = func(name)
        return result.upper()  # 新增:转大写
    return wrapper

# 装饰器2:加感叹号
def add_exclamation(func):
    def wrapper(name):
        result = func(name)
        return result + "!"  # 新增:加感叹号
    return wrapper

# 装饰器链:先执行@to_uppercase,再执行@add_exclamation
@add_exclamation
@to_uppercase
def greet(name):
    return f"你好,{name}"

print(greet("小红"))  # 输出:你好,小红 → 转大写→ 你好,小红 → 加感叹号→ 你好,小红!

 执行顺序秘诀:

  • 装饰器链的执行顺序是 "从下到上装饰,从上到下执行"
    就像穿衣服:先穿内衣(@to_uppercase),再穿外套(@add_exclamation);脱衣服时先脱外套,再脱内衣。

1.4 类装饰器:用类实现装饰功能

class CountCalls:
    def __init__(self, func):
        self.func = func  # 保存原函数
        self.count = 0  # 记录调用次数(状态)

    def __call__(self, *args, **kwargs):
        self.count += 1  # 每次调用+1
        print(f"这是第{self.count}次调用")
        return self.func(*args, **kwargs)  # 执行原函数

@CountCalls  # 用类装饰器
def multiply(a, b):
    return a * b

print(multiply(2, 3))  # 输出:这是第1次调用 → 6
print(multiply(4, 5))  # 输出:这是第2次调用 → 20

解析

  • 类装饰器需要实现__init__(接收原函数)和__call__(定义装饰逻辑)方法。
  • 优势:类可以保存状态(比如count变量),适合需要记录历史数据的场景。

1.5 实战场景:装饰器能帮你做什么?

1. 性能计时器:统计函数运行时间

import time

def timer(func):
    def wrapper(*args, **kwargs):
        start = time.time()  # 记录开始时间
        result = func(*args, **kwargs)  # 执行函数
        end = time.time()  # 记录结束时间
        print(f"{func.__name__}运行耗时:{end - start:.2f}秒")
        return result
    return wrapper

@timer
def slow_task():
    time.sleep(1.5)  # 模拟耗时操作

slow_task()  # 输出:slow_task运行耗时:1.50秒

    2. 日志记录器:自动保存函数的输入输出

    def log_io(func):
        def wrapper(*args, **kwargs):
            # 记录输入
            print(f"函数{func.__name__}被调用,参数:{args},{kwargs}")
            # 执行函数
            result = func(*args, **kwargs)
            # 记录输出
            print(f"函数{func.__name__}返回结果:{result}")
            return result
        return wrapper
    
    @log_io
    def add(a, b):
        return a + b
    
    add(3, 5)  # 输出:函数add被调用 → 参数:(3,5) → 返回结果:8

    3. 权限验证:限制只有管理员能执行函数

    def check_admin(func):
        def wrapper(*args, **kwargs):
            user = kwargs.get("user", "游客")  # 获取用户名
            if user != "admin":
                raise PermissionError("权限不足!只有管理员可操作")
            return func(*args, **kwargs)
        return wrapper
    
    @check_admin
    def delete_data(user):
        print("数据删除成功")
    
    delete_data(user="admin")  # 正常执行
    delete_data(user="user")   # 报错:PermissionError

    4. 参数校验:确保函数接收有效参数

    def validate_positive(func):
        def wrapper(a, b):
            if a <= 0 or b <= 0:
                raise ValueError("参数必须是正数!")
            return func(a, b)
        return wrapper
    
    @validate_positive
    def divide(a, b):
        return a / b
    
    print(divide(8, 2))  # 输出:4.0
    divide(-1, 5)  # 报错:ValueError

    5. 结果缓存:避免重复计算(提升效率)

    def cache_result(func):
        cache = {}  # 用字典保存计算结果:参数→结果
        def wrapper(n):
            if n in cache:
                print(f"从缓存获取{n}的结果")
                return cache[n]
            result = func(n)
            cache[n] = result  # 存入缓存
            return result
        return wrapper
    
    @cache_result
    def factorial(n):
        print(f"计算{n}的阶乘")
        if n == 1:
            return 1
        return n * factorial(n-1)
    
    print(factorial(5))  # 输出:计算5→4→3→2→1的阶乘 → 120
    print(factorial(5))  # 输出:从缓存获取5的结果 → 120(直接用缓存,不重复计算)

    1.6初学者必踩的 3 个坑(附解决方案)

    坑 1:装饰器导致函数名 "被替换"

    @timer
    def add(a, b):
        return a + b
    
    print(add.__name__)  # 输出:wrapper(原函数名变成了wrapper)

    解决:用functools.wraps保留原函数信息

    from functools import wraps
    
    def timer(func):
        @wraps(func)  # 关键:复制原函数信息到wrapper
        def wrapper(*args, **kwargs):
            # 装饰逻辑
        return wrapper
    
    @timer
    def add(a, b):
        return a + b
    
    print(add.__name__)  # 输出:add(正确保留原函数名)

    坑 2:带参数的装饰器少写一层函数

    # 错误示例:带参数的装饰器必须有三层函数
    def repeat(times):
        def wrapper():  # 少了中间层,会报错
            for _ in range(times):
                func()
        return wrapper

    正确写法:三层函数结构(外→参数,中→函数,内→逻辑)

    def repeat(times):  # 1. 接收参数
        def decorator(func):  # 2. 接收函数
            def wrapper():  # 3. 执行逻辑
                for _ in range(times):
                    func()
            return wrapper
        return decorator

    坑 3:装饰器链的执行顺序搞反

    @decorator1
    @decorator2
    def func():
        pass

    记住:装饰顺序是decorator2先装饰,decorator1后装饰;执行时decorator1的逻辑先执行,decorator2的逻辑后执行(从外到内,就近原则)。

     总结

            装饰器的核心价值 装饰器是Python中"代码复用"的神器——它让你可以把日志、权限、计时等通用功能抽离出来,像"插件"一样灵活地给函数"赋能"。 学会装饰器后,你会发现代码变得更简洁、更易维护:核心业务逻辑和辅助功能分离,既方便扩展,又不破坏原有代码。下次看到`@`开头的语法,你就知道:这是给函数"戴装备"呢!


    网站公告

    今日签到

    点亮在社区的每一天
    去签到