Python 教程 / 装饰器

装饰器

装饰器是Python最强大的特性之一,它允许我们在不修改原函数代码的情况下,增强函数的功能,实现代码复用和横切关注点分离。

什么是装饰器?

装饰器是一个接收函数作为参数,并返回新函数的函数。

┌─────────────────────────────────────────────────────────────────────┐
│                      装饰器原理图解                                  │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   装饰器本质上是一个高阶函数:                                      │
│                                                                     │
│   ┌─────────────────────────────────────────────────────────────┐   │
│   │                                                             │   │
│   │   decorator(func)  ──▶  new_func                           │   │
│   │                                                             │   │
│   │   输入: 原函数                                               │   │
│   │   输出: 包装后的新函数                                       │   │
│   │                                                             │   │
│   └─────────────────────────────────────────────────────────────┘   │
│                                                                     │
│   常见用途:                                                         │
│   • 日志记录                                                       │
│   • 性能计时                                                     │
│   • 权限检查                                                       │
│   • 输入验证                                                       │
│   • 缓存结果                                                       │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘ 
Python
# 不使用装饰器:手动包装
def say_hello():
    print("Hello!")

# 包装函数
def log_decorator(func):
    def wrapper():
        print("开始调用函数...")
        func()
        print("函数调用完成")
    return wrapper

# 手动装饰
logged_hello = log_decorator(say_hello)
logged_hello()
# 输出:
# 开始调用函数...
# Hello!
# 函数调用完成 

@ 语法糖

使用 @ 符号可以更简洁地应用装饰器。

┌─────────────────────────────────────────────────────────────────────┐
│                      @ 语法糖原理                                  │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   @decorator                                                       │
│   def func():                                                     │
│       pass                                                         │
│                                                                     │
│   等价于:                                                          │
│                                                                     │
│   def func():                                                     │
│       pass                                                         │
│   func = decorator(func)                                          │
│                                                                     │
│   执行顺序:                                                        │
│                                                                     │
│   1. 定义 func (原函数)                                          │
│   2. 调用 decorator(func),传入原函数                             │
│   3. 返回新函数                                                   │
│   4. 将新函数赋值给 func                                          │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘ 
Python
def log_decorator(func):
    def wrapper():
        print("开始调用函数...")
        func()
        print("函数调用完成")
    return wrapper

# 使用 @ 语法糖
@log_decorator
def say_hello():
    print("Hello!")

say_hello()
# 输出:
# 开始调用函数...
# Hello!
# 函数调用完成 

保留原函数元数据

装饰后的函数会丢失原函数的 __name____doc__ 等元数据。使用 functools.wraps 解决这个问题。

┌─────────────────────────────────────────────────────────────────────┐
│                    functools.wraps 作用                              │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   不使用 wraps:                                                   │
│   ┌─────────────────────────────────────────────────────────────┐   │
│   │  @decorator                                               │   │
│   │  def add(a, b):                                           │   │
│   │      """返回两个数的和"""                                  │   │
│   │      return a + b                                         │   │
│   │                                                             │   │
│   │  add.__name__  ──▶  "wrapper"  ❌                        │   │
│   │  add.__doc__   ──▶  None      ❌                         │   │
│   └─────────────────────────────────────────────────────────────┘   │
│                                                                     │
│   使用 wraps:                                                      │
│   ┌─────────────────────────────────────────────────────────────┐   │
│   │  @wraps(func)   ◀── 关键一步                              │   │
│   │  def wrapper(*args, **kwargs):                             │   │
│   │      ...                                                   │   │
│   │                                                             │   │
│   │  add.__name__  ──▶  "add"      ✓                        │   │
│   │  add.__doc__   ──▶  "返回两个数的和"  ✓                   │   │
│   └─────────────────────────────────────────────────────────────┘   │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘ 
Python
from functools import wraps

# 不使用 wraps
def bad_decorator(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

@bad_decorator
def add(a, b):
    """返回两个数的和"""
    return a + b

print(add.__name__)   # wrapper - 元数据丢失!
print(add.__doc__)    # None

# 使用 wraps 保留元数据
def good_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

@good_decorator
def add2(a, b):
    """返回两个数的和"""
    return a + b

print(add2.__name__)   # add2 - 元数据保留!
print(add2.__doc__)    # 返回两个数的和 

带参数的装饰器

装饰器工厂(三层嵌套)允许装饰器接受参数。

┌─────────────────────────────────────────────────────────────────────┐
│                    三层嵌套装饰器工厂                               │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   def decorator_factory(arg):     ← 第1层: 接收装饰器参数          │
│       def decorator(func):        ← 第2层: 接收被装饰的函数        │
│           def wrapper(...):       ← 第3层: 包装函数                 │
│               ...                                                    │
│           return wrapper                                             │
│       return decorator                                               │
│                                                                     │
│   调用链:                                                          │
│   @decorator_factory(arg)  ──▶ 返回 decorator 函数                │
│   @返回的decorator            ──▶ 装饰目标函数                     │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘ 
Python
from functools import wraps

# 装饰器工厂: repeat(times)
def repeat(times):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            results = []
            for _ in range(times):
                result = func(*args, **kwargs)
                results.append(result)
            return results
        return wrapper
    return decorator

@repeat(3)
def greet(name):
    return f"你好,{name}!"

print(greet("张三"))
# ['你好,张三!', '你好,张三!', '你好,张三!'] 

多层装饰器

同一个函数可以使用多个装饰器,按从近到远的顺序执行。

┌─────────────────────────────────────────────────────────────────────┐
│                    多层装饰器执行顺序                               │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   @decorator1                                                     │
│   @decorator2                                                     │
│   def func():                                                     │
│       pass                                                         │
│                                                                     │
│   等价于:                                                          │
│   func = decorator1(decorator2(func))                      │
│                                                                     │
│   执行顺序:                                                        │
│                                                                     │
│   func() 调用                                                      │
│       │                                                            │
│       ├── decorator1.wrapper 开始                                  │
│       │      │                                                      │
│       │      ├── decorator2.wrapper 开始                          │
│       │      │      │                                              │
│       │      │      ├── 原函数 func 执行                         │
│       │      │      │                                              │
│       │      │      └── decorator2.wrapper 结束                   │
│       │      │                                                      │
│       │      └── decorator1.wrapper 结束                          │
│       │                                                            │
│   结论: decorator2 先执行,decorator1 后执行                       │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘ 
Python
from functools import wraps

def decorator1(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print("decorator1 开始")
        result = func(*args, **kwargs)
        print("decorator1 结束")
        return result
    return wrapper

def decorator2(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print("decorator2 开始")
        result = func(*args, **kwargs)
        print("decorator2 结束")
        return result
    return wrapper

@decorator1
@decorator2
def say_hello():
    print("Hello!")
    return "done"

print(say_hello())
# 输出:
# decorator1 开始
# decorator2 开始
# Hello!
# decorator2 结束
# decorator1 结束
# done 

常用内置装饰器

@property - 将方法变为属性

Python
class Circle:
    def __init__(self, radius):
        self.radius = radius

    @property
    def area(self):
        return 3.14159 * self.radius ** 2

c = Circle(5)
print(c.area)       # 78.53975 (像访问属性一样)
# c.area = 100  # AttributeError: property 'area' has no setter 

@staticmethod 和 @classmethod

Python
class Math:
    @staticmethod
    def add(a, b):
        return a + b

    @classmethod
    def multiply(cls, a, b):
        return a * b

# 静态方法可以直接用类名调用,不需要实例化
print(Math.add(3, 4))      # 7

# 类方法接收类作为第一个参数
print(Math.multiply(3, 4))   # 12 

类装饰器

装饰器不仅可以是函数,也可以是类。

Python
class CountCalls:
    def __init__(self, func):
        self.func = func
        self.call_count = 0
        @wraps(func)

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

@CountCalls
def say_hi():
    print("Hi!")

say_hi()  # 函数被调用了 1 次\nHi!
say_hi()  # 函数被调用了 2 次\nHi! 

章节测验

问题1: 装饰器的本质是什么?

问题2: @wraps 的作用是什么?

问题3: 多层装饰器的执行顺序是?

问题4: 带参数的装饰器需要几层嵌套?

练习题

练习1: 计时装饰器

创建一个装饰器,记录函数执行时间。

练习2: 缓存装饰器

创建一个装饰器,缓存函数结果(使用字典)。

练习3: 验证装饰器

创建一个装饰器,验证函数参数是否为正数。

🐍 Python 在线代码编辑器
📝 运行结果