云计算百科
云计算领域专业知识百科平台

1️⃣2️⃣ 装饰器(Decorator)

在 Python 中,有一个非常强大的特性——装饰器(Decorator)。它能在不修改函数源代码的前提下,动态地为函数增加功能,比如打印日志、验证权限、计时统计等。这种"无侵入式"增强功能的方式,让代码更灵活、更易维护。今天我们就来深入理解装饰器的工作原理和用法。 通俗版:

Def xx1(被装饰函数):
def xx2(如果被装饰函数有参数那么输入):
xxxxxxxxxxxxxxx
被装饰函数(如果被装饰函数有参数那么输入)
xxxxxxxxxxxxxxx
# 如果被装饰函数中含有reture则需要返回被装饰函数
# 没有则不需要
reture xx2

文章目录

    • 一、 为什么需要装饰器?
      • 🧪 装饰器基础:从函数赋值说起
    • 二、 定义第一个装饰器:日志增强
      • 1. 基础装饰器结构
      • 2. 使用装饰器:@语法糖
      • 3. 原理拆解:
    • 三、 进阶:带参数的装饰器
      • 1. 定义带参数的装饰器
      • 2. 使用方式
      • 3. 调用效果
    • 四、 注意:函数身份信息的丢失
    • 五、 核心知识小结
    • 六、 练习
      • 🧩 练习1:实现一个计时装饰器
      • 🧩 练习2:实现兼容两种形式的装饰器
    • 七、 总结

一、 为什么需要装饰器?

假设我们有一个打印日期的函数:

def now():
print('2024-6-1')

现在需要给它加一个功能:调用前自动打印日志(比如“开始调用 now()”)。如果直接修改 now() 的代码,会有两个问题:

  • 多个函数需要加日志时,重复代码会非常繁琐;
  • 破坏原函数的封装性,后续维护难度增加。

装饰器的出现完美解决了这个问题:在不修改原函数代码的前提下,动态增强函数功能。

🧪 装饰器基础:从函数赋值说起

要理解装饰器,先看一个简单的函数特性:函数可以被赋值给变量,通过变量调用。

def now():
print('2024-6-1')

f = now # 将函数赋值给变量(注意没有括号)
f() # 调用变量,等价于 now() → 输出:2024-6-1
print(f.__name__) # 函数对象的__name__属性 → 输出:now

这一特性是装饰器的基础——装饰器本质是一个接收函数、返回新函数的高阶函数。


二、 定义第一个装饰器:日志增强

装饰器本质是一个返回函数的高阶函数。它接收原函数作为参数,返回一个包装后的新函数,新函数会在调用原函数的前后添加额外逻辑。

1. 基础装饰器结构

def log(func):
# 定义包装函数wrapper,用于增强原函数
def wrapper(*args, **kw):
# 调用原函数前:添加日志功能
print(f'call {func.__name__}():')
# 调用原函数,并返回结果
return func(*args, **kw)
# 返回包装后的函数
return wrapper

2. 使用装饰器:@语法糖

用 @log 标记需要增强的函数,等价于 now = log(now):

@log # 等价于 now = log(now)
def now():
print('2024-6-1')
now()
# 输出:
# call now(): # 装饰器新增的日志
# 2024-6-1 # 原函数的功能

3. 原理拆解:

  • @log 会将 now 函数作为参数传给 log;
  • log 函数返回 wrapper 函数,此时 now 变量指向 wrapper;
  • 调用 now() 实际是调用 wrapper(),先执行日志逻辑,再调用原 now()。
  • wrapper(*args, **kw) 中的 *args, **kw 是为了接收原函数的任意参数,保证无论原函数有多少参数,装饰器都能兼容。

    三、 进阶:带参数的装饰器

    如果需要自定义日志内容(比如打印“执行”或“调用”),装饰器需要三层嵌套:最外层接收参数,中间层接收原函数,内层是包装函数。

    1. 定义带参数的装饰器

    def log(text): # 最外层:接收装饰器参数
    def decorator(func): # 中间层:接收原函数
    def wrapper(*args, **kw): # 内层:包装函数
    print(f'{text} {func.__name__}():') # 使用参数text
    return func(*args, **kw)
    return wrapper # 返回包装函数
    return decorator # 返回中间层函数

    通俗版:

    def log(text): # 外层:接收装饰器的参数,比如 'execute'

    def 真正装饰器(被装饰的函数): # 中层:接收被装饰的函数

    def 包装函数(*args, **kwargs): # 内层:接收函数运行时的参数
    print(f'{text} {被装饰的函数.__name__}():') # 打印日志信息
    return 被装饰的函数(*args, **kwargs) # 调用原函数,并返回结果(如果原函数有返回值)

    return 包装函数 # 中层返回包装后的函数

    return 真正装饰器 # 外层返回装饰器

    2. 使用方式

    @log('execute') # 等价于 now = log('execute')(now)
    def now():
    print('2024-6-1')

    3. 调用效果

    now()
    # 输出:
    # execute now(): # 自定义的日志文本
    # 2024-6-1

    四、 注意:函数身份信息的丢失

    装饰器会改变原函数的元信息(如 __name__ 属性):

    print(now.__name__) # 输出:wrapper(而非原函数名now)

    这是因为 now 已指向 wrapper 函数。但有些代码依赖函数的原始名称(比如序列化、调试工具),这会导致问题。解决方法是用 functools.wraps 复制原函数的元信息。

    ✅ 正确写法:保留原函数信息

    import functools

    def log(func):
    @functools.wraps(func) # 复制原函数元信息到wrapper
    def wrapper(*args, **kw):
    print(f'call {func.__name__}():')
    return func(*args, **kw)
    return wrapper

    @log
    def now():
    print('2024-6-1')

    print(now.__name__) # 输出:now(正确保留原函数名)

    带参数的装饰器也需要同样处理:

    import functools

    def log(text):
    def decorator(func):
    @functools.wraps(func) # 关键:复制元信息
    def wrapper(*args, **kw):
    print(f'{text} {func.__name__}():')
    return func(*args, **kw)
    return wrapper
    return decorator

    五、 核心知识小结

    概念说明示例
    装饰器本质 返回函数的高阶函数 def log(func): return wrapper
    @语法糖 等价于 函数 = 装饰器(函数) @log → now = log(now)
    带参数的装饰器 三层嵌套,最外层接收参数 @log('execute')
    functools.wraps 保留原函数的__name__、文档等信息 @functools.wraps(func)
    wrapper参数设计 用*args, **kw兼容任意函数参数 def wrapper(*args, **kw):

    六、 练习

    🧩 练习1:实现一个计时装饰器

    尝试写一个装饰器,统计函数的执行时间:

    import time
    import functools

    def timer(func):
    @functools.wraps(func)
    def wrapper(*args, **kw):
    start = time.time()
    result = func(*args, **kw) # 调用原函数
    end = time.time()
    print(f'{func.__name__} 执行时间:{end start:.2f}秒')
    return result
    return wrapper

    # 测试
    @timer
    def slow_func():
    time.sleep(1) # 模拟耗时操作

    slow_func() # 输出:slow_func 执行时间:1.00秒

    🧩 练习2:实现兼容两种形式的装饰器

    需求:编写一个 log 装饰器,既能支持 @log(不带参数),又能支持 @log('execute')(带参数)。

    分析:

    • 不带参数时,装饰器直接接收被装饰的函数(可调用对象);
    • 带参数时,装饰器先接收参数(不可调用对象,如字符串),再返回处理函数的逻辑。

    可通过 callable() 函数判断参数类型,区分两种场景:

    import functools

    def log(arg):
    # 场景1:@log 形式(arg是被装饰的函数)
    if callable(arg):
    @functools.wraps(arg)
    def wrapper(*args, **kwargs):
    print(f'Call {arg.__name__}()') # 默认日志
    return arg(*args, **kwargs)
    return wrapper

    # 场景2:@log('xxx') 形式(arg是装饰器参数)
    else:
    def decorator(fn):
    @functools.wraps(fn)
    def wrapper(*args, **kwargs):
    print(f'{arg} {fn.__name__}()') # 自定义日志
    return fn(*args, **kwargs)
    return wrapper
    return decorator

    测试代码:

    # 测试不带参数
    @log
    def f1():
    print("执行 f1")
    f1()
    # 输出:Call f1() \\n 执行 f1

    # 测试带参数
    @log('execute')
    def f2():
    print("执行 f2")
    f2()
    # 输出:execute f2() \\n 执行 f2

    原理:

    • callable(arg) 判断 arg 是否为可调用对象(函数):
      • 是 → 进入 if 分支,直接处理被装饰函数;
      • 否 → 进入 else 分支,先接收参数,再返回处理函数的中间逻辑。

    七、 总结

    装饰器是 Python 函数式编程的精髓,它让代码更灵活、更易维护。核心要点:

  • 装饰器通过包装函数实现“无侵入式增强”;
  • 基础装饰器接收函数,带参数的装饰器需要三层嵌套;
  • 用 functools.wraps 保留原函数的元信息;
  • 适用于日志、计时、权限验证等场景。
  • 合理使用装饰器,可以让代码更简洁、更灵活,同时避免重复逻辑。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 1️⃣2️⃣ 装饰器(Decorator)
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!