当前位置:首页>python>【一起学 Python】第 25 天:装饰器(Decorator)

【一起学 Python】第 25 天:装饰器(Decorator)

  • 2026-09-07 06:18:31
【一起学 Python】第 25 天:装饰器(Decorator)
昨天我们学习了闭包(Closure)和 nonlocal  关键字,那是通往今天内容的基石。今天我们要解锁 Python 中最优雅、最强大的特性之一——装饰器(Decorator)。
装饰器是 Python 高级编程的门槛,掌握它意味着你真正理解了“函数是一等公民”的含义。

一、什么是装饰器?

1. 定义

装饰器(Decorator) 是一个用于增强函数功能的工具。它允许你在不修改原函数代码的前提下,为函数添加额外的逻辑(如日志、计时、权限验证等)。

2. 核心思想

  • 开放封闭原则(OCP):对扩展开放,对修改封闭。
  • 复用代码:将通用逻辑(如鉴权)提取出来,避免重复代码。

3. 本质

装饰器本质上是一个高阶函数:它接收一个函数作为参数,并返回一个新的函数。

二、装饰器的基础语法

1. 手动增强函数(不使用装饰器)

def my_func():    print("Hello")def wrapper():    print("Before")    my_func()    print("After")wrapper()  # 调用 wrapper 而不是 my_func

2. 使用装饰器语法糖(@)

def my_decorator(func):    def wrapper():        print("Before")        func()        print("After")    return wrapper@my_decorator  # 等价于 my_func = my_decorator(my_func)def my_func():    print("Hello")my_func()  # 直接调用原函数名,但执行了增强逻辑
💡 关键点:@my_decorator 放在函数定义上方,Python 会自动执行 my_func = my_decorator(my_func)。

三、通用装饰器模板(支持参数)

上面的例子只能装饰无参函数。实际工作中,函数通常有参数和返回值。

标准模板:

import functoolsdef decorator(func):    @functools.wraps(func)  # 保留原函数的元数据(__name__, __doc__)    def wrapper(*args, **kwargs):        # 1. 前置逻辑        print("Before execution")        # 2. 执行原函数        result = func(*args, **kwargs)        # 3. 后置逻辑        print("After execution")        # 4. 返回结果        return result    return wrapper

示例:保留元数据的重要性

# 不使用 functools.wrapsdef bad_decorator(func):    def wrapper(*args, **kwargs):        return func(*args, **kwargs)    return wrapper@bad_decoratordef hello():    """This is hello function"""    passprint(hello.__name__)  # 输出:wrapper (❌ 原函数名丢失)# 使用 functools.wrapsdef good_decorator(func):    @functools.wraps(func)    def wrapper(*args, **kwargs):        return func(*args, **kwargs)    return wrapper@good_decoratordef hello():    """This is hello function"""    passprint(hello.__name__)  # 输出:hello (✅ 正确)

四、带参数的装饰器

有时我们需要控制装饰器的行为,例如 @retry(times=3)。这需要三层嵌套。

结构:

  1. 外层函数:接收装饰器参数(如 times)。
  2. 中间函数:接收被装饰的函数(func)。
  3. 内层函数:接收被装饰函数的参数(*args)。

示例:重复执行装饰器

import functoolsdef repeat(times):    def decorator(func):        @functools.wraps(func)        def wrapper(*args, **kwargs):            for _ in range(times):                result = func(*args, **kwargs)            return result        return wrapper    return decorator@repeat(times=3)def say_hi():    print("Hi!")say_hi()# 输出:# Hi!# Hi!# Hi!

五、常见实战场景

1. 日志记录(Logging)

import functoolsimport logginglogging.basicConfig(level=logging.INFO)def log_func(func):    @functools.wraps(func)    def wrapper(*args, **kwargs):        logging.info(f"Calling {func.__name__} with {args}, {kwargs}")        result = func(*args, **kwargs)        logging.info(f"{func.__name__} returned {result}")        return result    return wrapper@log_funcdef add(a, b):    return a + badd(1, 2)

2. 性能计时(Timing)

import timeimport functoolsdef 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}s")        return result    return wrapper@timerdef slow_task():    time.sleep(1)slow_task()

3. 权限验证(Authentication)

def require_login(func):    @functools.wraps(func)    def wrapper(user, *args, **kwargs):        if not user.get("is_logged_in"):            return "Error: 请先登录"        return func(user, *args, **kwargs)    return wrapper@require_logindef view_profile(user):    return f"欢迎 {user['name']}"user = {"name": "Alice", "is_logged_in": False}print(view_profile(user))  # Error: 请先登录

4. 失败重试(Retry)

import timedef retry(max_attempts=3, delay=1):    def decorator(func):        @functools.wraps(func)        def wrapper(*args, **kwargs):            for i in range(max_attempts):                try:                    return func(*args, **kwargs)                except Exception as e:                    if i == max_attempts - 1:                        raise e                    print(f"失败,{delay}秒后重试...")                    time.sleep(delay)        return wrapper    return decorator@retry(max_attempts=3, delay=1)def connect_db():    import random    if random.random() < 0.7:        raise ConnectionError("连接失败")    return "连接成功"# 运行多次直到成功或耗尽次数try:    print(connect_db())except Exception as e:    print(f"最终失败:{e}")

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

当一个函数被多个装饰器修饰时,顺序很重要。

def decorator_a(func):    def wrapper():        print("A 前")        func()        print("A 后")    return wrapperdef decorator_b(func):    def wrapper():        print("B 前")        func()        print("B 后")    return wrapper@decorator_a@decorator_bdef say_hello():    print("Hello")say_hello()

输出结果:

A 前B 前HelloB 后A 后

💡 记忆法则:

  • 定义顺序:从下往上(先 @b 后 @a)。
  • 执行顺序:从外往内(先 A 前 后 B 前)。
  • 相当于 say_hello = decorator_a(decorator_b(say_hello))。

七、类装饰器(Class Decorator)

装饰器不仅可以是函数,也可以是类(实现 __call__ 方法)。

class CountCalls:    def __init__(self, func):        self.func = func        self.count = 0    def __call__(self, *args, **kwargs):        self.count += 1        print(f"调用次数:{self.count}")        return self.func(*args, **kwargs)@CountCallsdef say_hello():    print("Hello")say_hello()  # 调用次数:1say_hello()  # 调用次数:2

八、最佳实践与注意事项

✅ 推荐做法

  1. 始终使用 @functools.wraps:保留原函数信息,方便调试和文档生成。
  2. 使用 *args, **kwargs:确保装饰器通用,不限制原函数参数。
  3. 保持单一职责:一个装饰器只做一件事(如只负责计时,或只负责日志)。
  4. 命名清晰:装饰器名称应反映其功能(如 @login_required, @cache)。

❌ 避免做法

  1. 副作用过大:装饰器不应改变原函数的核心逻辑返回值(除非是设计好的,如缓存)。
  2. 过度嵌套:超过 3 层装饰器会让代码难以阅读和调试。
  3. 性能损耗:避免在装饰器中执行耗时操作(如网络请求),除非必要。

九、实战练习

练习 1:编写缓存装饰器

编写一个 @cache 装饰器,缓存函数的返回值。如果相同的参数再次传入,直接返回缓存结果,不再执行函数。

参考

import functoolsdef cache(func):    cached_results = {}    @functools.wraps(func)    def wrapper(*args):        if args in cached_results:            print("命中缓存")            return cached_results[args]        result = func(*args)        cached_results[args] = result        return result    return wrapper@cachedef expensive_calc(x):    print("计算中...")    return x * xprint(expensive_calc(5))  # 计算中... 25print(expensive_calc(5))  # 命中缓存 25

练习 2:编写权限等级装饰器

编写一个 @require_level(level) 装饰器,只有当用户的 level 大于等于指定值时才能执行函数。

参考

import functoolsdef require_level(min_level):    def decorator(func):        @functools.wraps(func)        def wrapper(user, *args, **kwargs):            if user.get("level", 0) < min_level:                return f"权限不足:需要等级 {min_level}"            return func(user, *args, **kwargs)        return wrapper    return decorator@require_level(5)def admin_panel(user):    return "欢迎进入管理面板"user = {"name": "Bob", "level": 3}print(admin_panel(user))  # 权限不足:需要等级 5

十、总结

概念

说明

本质

高阶函数(接收函数,返回函数)

语法糖

@decorator 等价于 func = decorator(func)

通用模板

使用 *args, **kwargs 和 functools.wraps

带参装饰器

需要三层嵌套(参数 -> 函数 -> 参数)

执行顺序

多个装饰器时,从下往上定义,从外往内执行

应用场景

日志、计时、鉴权、缓存、重试


最新文章

随机文章