Python 装饰器

装饰器(decorator)可以在不修改原函数代码的前提下,动态扩展函数或类的功能。本质上装饰器就是一个函数——接收一个函数作为参数,返回一个新函数。

一、基本概念

def my_decorator(func):
    def wrapper():
        print("函数执行前")
        func()
        print("函数执行后")
    return wrapper

@my_decorator
def say_hello():
    print("Hello!")

say_hello()
# 函数执行前
# Hello!
# 函数执行后

@my_decorator 是语法糖,等价于:

def say_hello():
    print("Hello!")

say_hello = my_decorator(say_hello)

所以调用 say_hello() 时,实际执行的是 wrapper()wrapper 在原函数前后插入了额外逻辑。

二、带参数的装饰器

如果原函数有参数,wrapper 需要用 *args, **kwargs 接收所有参数:

def my_decorator(func):
    def wrapper(*args, **kwargs):
        print("执行前")
        result = func(*args, **kwargs)
        print("执行后")
        return result
    return wrapper

@my_decorator
def greet(name, msg="Hello"):
    print(f"{msg}, {name}!")

greet("Alice", msg="Hi")
# 执行前
# Hi, Alice!
# 执行后

三、带参数的装饰器(装饰器工厂)

当装饰器本身也需要接收参数时,需要在外面再套一层函数,变成三层嵌套:

def repeat(num_times):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for _ in range(num_times):
                func(*args, **kwargs)
        return wrapper
    return decorator

@repeat(3)
def say_hello():
    print("Hello!")

say_hello()
# Hello!
# Hello!
# Hello!

执行过程:@repeat(3) → 先调用 repeat(3) 返回 decoratordecorator 接收 say_hello 返回 wrappersay_hello 被替换为 wrapper

四、实战:记录函数执行时间

import time

def runtime(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"函数 {func.__name__} 运行时间:{end - start:.4f}秒")
        return result
    return wrapper

@runtime
def slow_function():
    time.sleep(2)
    return "done"

slow_function()
# 函数 slow_function 运行时间:2.0001秒

五、多个装饰器堆叠

多个装饰器从下到上依次应用,从外到内依次执行:

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

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

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

say_hello()
# Decorator 1 开始
# Decorator 2 开始
# Hello!
# Decorator 2 结束
# Decorator 1 结束

@decorator1@decorator2 的执行顺序相当于 decorator1(decorator2(say_hello)),所以 decorator1 在最外层,最先执行。

六、保留原函数信息 functools.wraps

装饰器会替换原函数,导致原函数的 __name____doc__ 等属性丢失。用 functools.wraps 修复:

from functools import wraps

def my_decorator(func):
    @wraps(func)   # 保留原函数的元信息
    def wrapper(*args, **kwargs):
        """wrapper 的文档"""
        return func(*args, **kwargs)
    return wrapper

@my_decorator
def say_hello():
    """这是 say_hello 的文档"""
    print("Hello!")

print(say_hello.__name__)   # say_hello(不加 wraps 会变成 wrapper)
print(say_hello.__doc__)    # 这是 say_hello 的文档

七、常见应用场景

7.1 计时装饰器(通用版)

import time
from functools import wraps

def timer(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        elapsed = time.time() - start
        print(f"{func.__name__} 耗时 {elapsed:.4f}s")
        return result
    return wrapper

@timer
def add(a, b):
    time.sleep(1)
    return a + b

print(add(1, 2))  # 3

7.2 日志装饰器

from functools import wraps

def log(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print(f"调用 {func.__name__},参数: {args}, {kwargs}")
        result = func(*args, **kwargs)
        print(f"{func.__name__} 返回: {result}")
        return result
    return wrapper

@log
def multiply(a, b):
    return a * b

multiply(3, 4)
# 调用 multiply,参数: (3, 4), {}
# multiply 返回: 12

7.3 缓存装饰器(函数结果缓存)

from functools import wraps, lru_cache

@lru_cache(maxsize=128)
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

print(fibonacci(30))    # 832040(第一次计算后缓存结果)
print(fibonacci.cache_info())  # 查看缓存命中率

自己实现一个简易缓存装饰器:

from functools import wraps

def memoize(func):
    cache = {}
    @wraps(func)
    def wrapper(*args):
        if args not in cache:
            cache[args] = func(*args)
        return cache[args]
    return wrapper

@memoize
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

print(fibonacci(100))   # 354224848179261915075

7.4 权限校验装饰器

from functools import wraps

def require_role(role):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            user_role = kwargs.get("role") or (args[0] if args else None)
            if user_role != role:
                print(f"权限不足,需要 {role} 角色")
                return None
            return func(*args, **kwargs)
        return wrapper
    return decorator

@require_role("admin")
def delete_user(username):
    print(f"删除用户 {username}")

delete_user("alice")              # 权限不足,需要 admin 角色
delete_user("alice", role="admin") # 删除用户 alice

7.5 重试装饰器

import time
from functools import wraps

def retry(max_attempts=3, delay=1):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(max_attempts):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    print(f"第 {attempt + 1} 次尝试失败: {e}")
                    if attempt < max_attempts - 1:
                        time.sleep(delay)
            print(f"重试 {max_attempts} 次后仍然失败")
            return None
        return wrapper
    return decorator

@retry(max_attempts=3, delay=2)
def unstable_function():
    import random
    if random.random() < 0.7:
        raise ConnectionError("网络超时")
    return "成功"

print(unstable_function())

八、类装饰器

除了函数,装饰器也可以用类来实现。

8.1 类作为装饰器

类中定义 __call__ 方法,让实例可以像函数一样被调用:

class CountCalls:
    def __init__(self, func):
        self.func = func
        self.count = 0

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

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

say_hello()  # say_hello 被调用了 1 次 → Hello!
say_hello()  # say_hello 被调用了 2 次 → Hello!

8.2 用类装饰器实现单例模式

class Singleton:
    def __init__(self, cls):
        self.cls = cls
        self.instance = None

    def __call__(self, *args, **kwargs):
        if self.instance is None:
            self.instance = self.cls(*args, **kwargs)
        return self.instance

@Singleton
class Database:
    def __init__(self):
        print("初始化数据库")

db1 = Database()  # 初始化数据库
db2 = Database()  # 没有输出,直接复用
print(db1 is db2)  # True

九、内置装饰器

Python 自带了一些常用的装饰器:

class MyClass:
    def __init__(self):
        self._name = ""

    @staticmethod          # 静态方法 — 不需要 self/cls,类和实例都能调用
    def static_func():
        print("静态方法")

    @classmethod           # 类方法 — 第一个参数是 cls(类本身)
    def class_func(cls):
        print(f"类方法,类名: {cls.__name__}")

    @property              # 属性装饰器 — 方法变属性,支持 getter/setter
    def name(self):
        return self._name

    @name.setter
    def name(self, value):
        self._name = value

obj = MyClass()
obj.static_func()    # 静态方法
MyClass.class_func() # 类方法,类名: MyClass
obj.name = "Alice"
print(obj.name)      # Alice

十、用在类上

装饰器也可以修饰整个类,常用于自动注册、单例等场景:

registry = {}

def register(cls):
    registry[cls.__name__] = cls
    return cls

@register
class Dog:
    pass

@register
class Cat:
    pass

print(registry)  # {'Dog': <class '__main__.Dog'>, 'Cat': <class '__main__.Cat'>}

在 Flask/Django 等框架中大量使用这种模式来实现路由注册。