为什么你的Python代码那么乱?因为你不会用装饰器

你有没有遇到过这种情况:

项目里到处都是这样的代码:

python 复制代码
def user_login():
    start_time = time.time()
    print(f"[{datetime.now()}] 开始执行用户登录")

    try:
        # 业务逻辑
        result = authenticate_user()
        print(f"[{datetime.now()}] 用户登录成功")
        return result
    except Exception as e:
        print(f"[{datetime.now()}] 用户登录失败: {e}")
        raise
    finally:
        end_time = time.time()
        print(f"[{datetime.now()}] 登录耗时: {end_time - start_time:.2f}秒")

def user_register():
    start_time = time.time()
    print(f"[{datetime.now()}] 开始执行用户注册")

    try:
        # 业务逻辑
        result = create_user()
        print(f"[{datetime.now()}] 用户注册成功")
        return result
    except Exception as e:
        print(f"[{datetime.now()}] 用户注册失败: {e}")
        raise
    finally:
        end_time = time.time()
        print(f"[{datetime.now()}] 注册耗时: {end_time - start_time:.2f}秒")

每个函数都要重复写日志、异常处理、性能统计的代码...

然后你百度了一圈,Stack Overflow翻了三页,最后发现有个叫"装饰器"的东西。

今天咱们就来彻底搞懂这个让代码瞬间变优雅的神器。

装饰器到底是啥玩意儿?

一句话解释:装饰器就是一个"包装工",它把你的函数重新包装一下,加点新功能。

想象一下你去奶茶店:

ini 复制代码
原始函数 = 基础奶茶(珍珠奶茶)
装饰器 = 加料服务(加珍珠、加椰果、加布丁)
@decorator = 直接说"我要加珍珠"

你没加装饰器之前:

python 复制代码
# 普通珍珠奶茶
def get_milk_tea():
    return "珍珠奶茶"

加了装饰器之后:

python 复制代码
@add_pearls  # 相当于:get_milk_tea = add_pearls(get_milk_tea)
def get_milk_tea():
    return "珍珠奶茶"

# 现在这杯奶茶自动加了珍珠,还送了你吸管

本质上,@decorator 只是语法糖,真正的执行是:

python 复制代码
# 这两种写法完全等价:
@decorator
def func():
    pass

# 等价于:
def func():
    pass
func = decorator(func)

先看看最简单的装饰器

python 复制代码
import time
from functools import wraps

def timer_decorator(func):
    @wraps(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:.2f}秒")
        return result
    return wrapper

@timer_decorator
def slow_function():
    time.sleep(1)
    return "我执行完了"

result = slow_function()
# 输出: slow_function 执行耗时: 1.00秒

看到了吗?原本需要每个函数都写的计时代码,现在只需要一个@timer_decorator就搞定了!

但是!别急着用,先搞懂这几个坑

坑1:函数信息丢失的问题

错误写法:

python 复制代码
def timer_decorator(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:.2f}秒")
        return result
    return wrapper

@timer_decorator
def my_awesome_function():
    """这个函数很厉害,能拯救世界"""
    pass

print(my_awesome_function.__name__)  # 输出: wrapper (不是my_awesome_function!)
print(my_awesome_function.__doc__)   # 输出: None (文档说明丢了!)

正确写法:

python 复制代码
from functools import wraps

def timer_decorator(func):
    @wraps(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:.2f}秒")
        return result
    return wrapper

@timer_decorator
def my_awesome_function():
    """这个函数很厉害,能拯救世界"""
    pass

print(my_awesome_function.__name__)  # 输出: my_awesome_function ✓
print(my_awesome_function.__doc__)   # 输出: 这个函数很厉害,能拯救世界 ✓

@wraps(func) 就是函数信息的"复印机",把原函数的名字、文档、参数信息全部复制到装饰器函数上。

坑2:带参数的装饰器

有时候你想给装饰器传参数,比如:

python 复制代码
@repeat(3)  # 执行3次
def test_function():
    print("测试中...")

这时候就需要装饰器工厂

python 复制代码
def repeat(times):
    def decorator(func):
        @wraps(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(3)
def test_function():
    print("测试中...")
    return "成功"

# 等价于:test_function = repeat(3)(test_function)
results = test_function()

记住这个公式:带参数的装饰器 = 三层嵌套函数

坑3:装饰器的执行顺序

python 复制代码
@decorator1
@decorator2
def my_function():
    pass

# 执行顺序相当于:my_function = decorator1(decorator2(my_function))
# 也就是:先执行decorator2,再执行decorator1

实战:解决真实项目中的重复代码

场景1:API接口的日志记录

重复代码地狱:

python 复制代码
def get_user_info(user_id):
    print(f"[{datetime.now()}] API调用: get_user_info, 参数: {user_id}")

    try:
        result = database.query_user(user_id)
        print(f"[{datetime.now()}] API调用成功: 返回用户信息")
        return {"status": "success", "data": result}
    except DatabaseError as e:
        print(f"[{datetime.now()}] 数据库错误: {e}")
        return {"status": "error", "message": "数据库查询失败"}
    except Exception as e:
        print(f"[{datetime.now()}] 未知错误: {e}")
        return {"status": "error", "message": "服务器内部错误"}

def create_user(user_data):
    print(f"[{datetime.now()}] API调用: create_user, 参数: {user_data}")

    try:
        result = database.insert_user(user_data)
        print(f"[{datetime.now()}] API调用成功: 用户创建成功")
        return {"status": "success", "data": result}
    except DatabaseError as e:
        print(f"[{datetime.now()}] 数据库错误: {e}")
        return {"status": "error", "message": "数据库插入失败"}
    except Exception as e:
        print(f"[{datetime.now()}] 未知错误: {e}")
        return {"status": "error", "message": "服务器内部错误"}

优雅的装饰器方案:

python 复制代码
from functools import wraps
import json
from datetime import datetime

def api_logger(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        # 记录请求
        print(f"[{datetime.now()}] API调用: {func.__name__}, 参数: {args}, {kwargs}")

        try:
            result = func(*args, **kwargs)
            print(f"[{datetime.now()}] API调用成功: {func.__name__}")
            return {"status": "success", "data": result}
        except DatabaseError as e:
            print(f"[{datetime.now()}] 数据库错误: {e}")
            return {"status": "error", "message": "数据库操作失败"}
        except Exception as e:
            print(f"[{datetime.now()}] 未知错误: {e}")
            return {"status": "error", "message": "服务器内部错误"}
    return wrapper

@api_logger
def get_user_info(user_id):
    return database.query_user(user_id)

@api_logger
def create_user(user_data):
    return database.insert_user(user_data)

# 看看多简洁!业务逻辑和日志处理完全分离

场景2:权限验证

python 复制代码
def admin_required(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        # 假设从session或token中获取用户信息
        current_user = get_current_user()

        if not current_user:
            raise PermissionError("请先登录")

        if not current_user.is_admin:
            raise PermissionError("需要管理员权限")

        return func(*args, **kwargs)
    return wrapper

@admin_required
def delete_user(user_id):
    # 只有管理员能删除用户
    database.delete_user(user_id)

@admin_required
def view_system_logs():
    # 只有管理员能查看系统日志
    return database.get_system_logs()

场景3:缓存装饰器

python 复制代码
import time
from functools import wraps

def cache(expire_time=60):
    """缓存装饰器,expire_time单位是秒"""
    def decorator(func):
        func._cache = {}  # 把缓存存储在函数的属性里

        @wraps(func)
        def wrapper(*args, **kwargs):
            # 生成缓存key(把参数转成字符串)
            cache_key = str(args) + str(sorted(kwargs.items()))

            current_time = time.time()

            # 检查缓存是否存在且未过期
            if cache_key in func._cache:
                cached_result, cached_time = func._cache[cache_key]
                if current_time - cached_time < expire_time:
                    print(f"使用缓存: {func.__name__}({cache_key})")
                    return cached_result

            # 执行原函数
            result = func(*args, **kwargs)

            # 存入缓存
            func._cache[cache_key] = (result, current_time)
            print(f"缓存结果: {func.__name__}({cache_key})")

            return result
        return wrapper
    return decorator

@cache(expire_time=30)  # 缓存30秒
def expensive_calculation(x, y):
    print(f"执行耗时计算: {x} + {y}")
    time.sleep(2)  # 模拟耗时操作
    return x + y

# 第一次调用会执行计算
result1 = expensive_calculation(1, 2)  # 2秒后才返回结果

# 第二次调用直接从缓存返回,瞬间完成
result2 = expensive_calculation(1, 2)  # 瞬间返回结果

高级玩法:类装饰器

有时候装饰器需要保存状态,用类更方便:

python 复制代码
class CountCalls:
    def __init__(self, func):
        self.func = func
        self.call_count = 0
        wraps(func)(self)  # 让类实例表现得像原函数

    def __call__(self, *args, **kwargs):
        self.call_count += 1
        print(f"{self.func.__name__} 被调用了 {self.call_count} 次")
        return self.func(*args, **kwargs)

@CountCalls
def hello():
    print("Hello!")

hello()  # 输出: hello 被调用了 1 次
hello()  # 输出: hello 被调用了 2 次
hello()  # 输出: hello 被调用了 3 次

print(f"总共调用了 {hello.call_count} 次")

总结:什么时候用装饰器?

适合用装饰器的场景:

  • ✅ 重复的功能代码(日志、计时、缓存、权限验证)
  • ✅ 想在不修改原函数的情况下增加功能
  • ✅ 需要横切多个函数的关注点(AOP思想)
  • ✅ 想让代码更优雅、更可维护

不适合用装饰器的场景:

  • ❌ 只在一个函数里用的逻辑
  • ❌ 和原函数逻辑耦合太紧的功能
  • ❌ 调试时需要频繁查看函数内部状态的情况

记住一句话:装饰器是代码的"化妆品",不是"手术刀"。它用来美化代码,不是用来修改核心逻辑的。

下次再看到满屏的重复代码,你就知道该怎么办了。

别让你的代码看起来像个垃圾堆,用装饰器让它优雅起来!

相关推荐
悟空码字1 小时前
SpringBoot实现日志系统,Bug现形记
java·spring boot·后端
狂奔小菜鸡1 小时前
Day24 | Java泛型通配符与边界解析
java·后端·java ee
xjz18421 小时前
ThreadPoolExecutor线程回收流程详解
后端
天天摸鱼的java工程师1 小时前
🐇RabbitMQ 从入门到业务实战:一个 Java 程序员的实战手记
java·后端
Frank_zhou1 小时前
CopyOnWriteArrayList
后端
楚兴1 小时前
使用 Eino 和 Ollama 构建智能 Go 应用:从简单问答到复杂 Agent
人工智能·后端
小镇cxy2 小时前
VibeCoding实践,Spec+Claude Code小程序开发
后端·claude·vibecoding
GeekPMAlex2 小时前
深入理解 Python 元组、哈希、堆与 enumerate
后端
踏浪无痕2 小时前
从单体PHP到微服务:一个五年老项目的血泪重构史
后端·面试·架构