Python 装饰器


前言

很早就接触装饰器了,刚接触的时候完全看不懂它的语法,搞不明白它的作用,尤其是看到多个装饰器可以装饰同一个函数,并且有的装饰器还能传参,经常被搞的一头雾水,后来有了一些自己的理解,也是只会简单的使用装饰器,但是自己编写还是要借助资料或AI,比较头疼。所以本文的目的是学习并掌握装饰器的anything,以达到对python的装饰器运用自如的境界

一、什么是装饰器

本质上就是一个函数,可以为其他函数增加额外功能

⚠️注意:装饰器返回的是一个函数,因为它的作用就是给传入的原函数增加额外的功能


二、为什么要用装饰器

一看就懂的例子:比如说10个模特要依次出场,每个模特都需要带一个手链,但是买10个手链成本又很高,可以只买一个手链让她们轮流复用即可。看到这里大概就知道为什么要用装饰器了吧😃

1. 代码复用与模块化

装饰器允许你将一些通用功能(如日志记录、性能测试、权限验证等)抽离出来,形成独立的装饰器函数,然后可以轻松地应用到多个目标函数上。

例如,如果你需要为多个函数添加 “执行时间统计” 功能,无需在每个函数中重复编写计时代码,只需定义一个计时装饰器,再用它修饰这些函数即可。

2. 保持代码简洁与可读性

如果不使用装饰器,要给函数添加额外功能,通常有两种方式:

  • 直接修改函数内部代码(破坏了原函数的封装性)
  • 在调用函数时手动添加额外逻辑(导致代码冗余且分散)

装饰器通过 @装饰器名 的语法,清晰地表明了 “这个函数被增强了”,既不侵入原函数代码,又让增强逻辑的位置非常明确。

3. 面向切面编程(AOP)

装饰器是实现 AOP 思想的重要工具。AOP 强调将 “核心业务逻辑” 与 “辅助功能”(如日志、事务、缓存)分离,装饰器恰好能实现这种分离:

  • 核心逻辑写在原函数中
  • 辅助功能写在装饰器中
  • 通过装饰器将两者 “编织” 在一起

三、如何编写&调用

1. 不带参数的装饰器

  • 简单编写一个 统计函数耗时装饰器 elapsed
import time

def elapsed(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

可以发现,装饰器函数的内部wrapper函数的型参使用的是万能型参 *args, **kwargs保证函数的参数可以正确传入

  • 调用这个elapsed装饰器
@elapsed
def power_sum(n):
    """计算 1~n 的平方和"""
    total = 0
    for i in range(1, n + 1):
        total += i ** 2
    return total

💡@elapsed 是语法糖的写法 等价于下面的调用

power_sum = elapsed(power_sum)
  • 输出
函数 power_sum 执行耗时: 1.28131341666667916666675000000

2. 带参数的装饰器

带参数的装饰器可以理解为一个 “装饰器工厂”,它先接收参数,然后返回一个真正的装饰器函数。这种装饰器的实现需要三层函数嵌套:

  1. 最外层函数:接收装饰器的参数
  2. 中间层函数:真正的装饰器,接收被装饰的函数
  3. 最内层函数:包装函数,实现具体的装饰逻辑
  • 改进的统计函数耗时装饰器 elapsed(可以通过参数指定运行时间小数点精度的):
import time

def elapsed_precision(precision=6):
    def decorator(func):
        """目标函数执行的耗时"""
        def wrapper(*args, **kwargs):
            start_time = time.time()
            result = func(*args, **kwargs)
            end_time = time.time()
            print(f"函数 {func.__name__} 执行耗时: {round(end_time - start_time, precision)} 秒")
            return result
        return wrapper
    return decorator
  • 调用这个elapsed_precisioin装饰器
@elapsed_precision(3)  # 保留三位小数
def power_sum(n):
    """计算 1~n 的平方和"""
    total = 0
    for i in range(1, n + 1):
        total += i ** 2
    return total
  • 输出
函数 power_sum 执行耗时: 1.32241666667916666675000000

四. 装饰器运行的顺序

在函数定义阶段:执行顺序是从最靠近函数的装饰器开始,自内而外的执行
在函数执行阶段:执行顺序由外而内,一层层执行

def deco1(func):
  def wrapper():
    print('deco1 start')
    func()
    print('deco1 end')
  return wrapper

def deco2(func):
  def wrapper():
    print('deco2 start')
    func()
    print('deco2 end')
  return wrapper

@deco1
@deco2
def my_func():
  print('original function')

my_func()

该示例中,my_func 函数被 deco1deco2 装饰器修饰。在函数执行时,实际上等价于以下代码:

my_func = deco1(deco2(my_func))
my_func()

运行结果如下:

deco1 start
deco2 start
original function
deco2 end
deco1 end

很难用语言描述这个顺序,意会一下。因为定义的时候返回的是函数对象,并没有执行,然后在执行的时候就是最外层最先开始执行。

五、常见使用场景

  • 日志记录:自动记录函数的调用参数、返回值、调用时间等
  • 性能分析:统计函数的执行时间
  • 权限验证:在函数执行前检查用户是否有权限
  • 缓存:缓存函数的计算结果,避免重复计算
  • 输入验证:检查函数参数是否符合要求

  目录