首页 / 知识库 / 0基础入门-阅读资料 / 0基础-python入门到精通

第 7 章 装饰器

7.1 函数是一等公民

在 Python 中,函数和整数、字符串一样,是普通的对象:

def shout(text):
    return text.upper()

# 函数可以赋值给变量
yell = shout
print(yell("hello"))  # HELLO

# 函数可以存放在数据结构中
funcs = [str.upper, str.lower, str.title]
for f in funcs:
    print(f("hello world"))
# HELLO WORLD
# hello world
# Hello World

# 函数可以作为参数传递
def apply(func, value):
    return func(value)

print(apply(len, "hello"))  # 5
print(apply(sorted, [3, 1, 2]))  # [1, 2, 3]

# 函数可以在函数内部定义
def make_adder(n):
    def adder(x):
        return x + n
    return adder

add5 = make_adder(5)
print(add5(10))  # 15

7.2 装饰器原理:从手动包装到 @ 语法糖

装饰器本质上就是一个接收函数并返回新函数的函数。

手动包装

import time

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

def slow_function():
    time.sleep(1)
    return "done"

# 手动包装
slow_function = timer(slow_function)
slow_function()  # slow_function 耗时 1.0012s

@ 语法糖

上面的 slow_function = timer(slow_function) 可以用 @ 简写:

@timer
def slow_function():
    time.sleep(1)
    return "done"

# 完全等价于:
# slow_function = timer(slow_function)

slow_function()  # slow_function 耗时 1.0012s

装饰器的执行流程

def my_decorator(func):
    print(f"装饰 {func.__name__}")  # 在装饰时执行,不是调用时
    
    def wrapper(*args, **kwargs):
        print("调用前")
        result = func(*args, **kwargs)
        print("调用后")
        return result
    return wrapper

@my_decorator       # 这一行会立即执行 my_decorator(say_hello)
def say_hello():    # 输出: 装饰 say_hello
    print("Hello!")

print("---")
say_hello()
# ---
# 调用前
# Hello!
# 调用后

7.3 带参数的装饰器

如果装饰器本身需要参数,需要再嵌套一层:

def repeat(n):
    def decorator(func):
        def wrapper(*args, **kwargs):
            results = []
            for _ in range(n):
                results.append(func(*args, **kwargs))
            return results
        return wrapper
    return decorator

@repeat(3)          # 先调用 repeat(3) 得到 decorator,再用 decorator 装饰
def greet(name):
    print(f"Hello, {name}!")
    return name

greet("Alice")
# Hello, Alice!
# Hello, Alice!
# Hello, Alice!
# 实用:重试装饰器
import time

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

@retry(max_attempts=3, delay=0.5)
def unreliable_api():
    import random
    if random.random() < 0.7:
        raise ConnectionError("网络错误")
    return "成功"

7.4 functools.wraps 保留元信息

装饰后,函数的 __name____doc__ 等属性会丢失:

def timer(func):
    def wrapper(*args, **kwargs):
        """wrapper 的文档"""
        return func(*args, **kwargs)
    return wrapper

@timer
def my_func():
    """my_func 的文档"""
    pass

print(my_func.__name__)  # wrapper — 丢失了原函数名!
print(my_func.__doc__)   # wrapper 的文档 — 丢失了原文档!

functools.wraps 解决:

from functools import wraps

def timer(func):
    @wraps(func)    # 把 func 的元信息复制到 wrapper
    def wrapper(*args, **kwargs):
        """wrapper 的文档"""
        return func(*args, **kwargs)
    return wrapper

@timer
def my_func():
    """my_func 的文档"""
    pass

print(my_func.__name__)  # my_func ✓
print(my_func.__doc__)   # my_func 的文档 ✓

# 还可以通过 __wrapped__ 访问原函数
print(my_func.__wrapped__)  # <function my_func at 0x...>

规则:写装饰器时,总是使用 @functools.wraps

7.5 类装饰器

用类实现装饰器

类只要实现了 __call__ 方法就可以作为装饰器:

from functools import wraps

class CountCalls:
    def __init__(self, func):
        wraps(func)(self)    # 等价于用 @wraps
        self.func = func
        self.call_count = 0
    
    def __call__(self, *args, **kwargs):
        self.call_count += 1
        print(f"{self.func.__name__} 被调用了 {self.call_count} 次")
        return self.func(*args, **kwargs)

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

say_hello()  # say_hello 被调用了 1 次 \n Hello!
say_hello()  # say_hello 被调用了 2 次 \n Hello!
print(say_hello.call_count)  # 2

用装饰器装饰类

装饰器不仅能装饰函数,也能装饰类:

def singleton(cls):
    instances = {}
    
    @wraps(cls)
    def get_instance(*args, **kwargs):
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]
    
    return get_instance

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

db1 = Database()  # 初始化数据库连接
db2 = Database()  # 不会再初始化
print(db1 is db2) # True
# 给类自动添加方法
def add_repr(cls):
    def __repr__(self):
        attrs = ", ".join(f"{k}={v!r}" for k, v in self.__dict__.items())
        return f"{cls.__name__}({attrs})"
    cls.__repr__ = __repr__
    return cls

@add_repr
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

print(Point(3, 4))  # Point(x=3, y=4)

7.6 多个装饰器的叠加顺序

def bold(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        return f"<b>{func(*args, **kwargs)}</b>"
    return wrapper

def italic(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        return f"<i>{func(*args, **kwargs)}</i>"
    return wrapper

@bold
@italic
def greet(name):
    return f"Hello, {name}"

print(greet("Alice"))
# <b><i>Hello, Alice</i></b>

# 执行顺序:从下往上装饰,从外往内执行
# 等价于:greet = bold(italic(greet))
# 调用时:bold 的 wrapper → italic 的 wrapper → greet

7.7 常见实战场景

计时器

from functools import wraps
import time

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

缓存

from functools import lru_cache

@lru_cache(maxsize=128)
def expensive_compute(n):
    time.sleep(0.1)  # 模拟耗时计算
    return n ** 2

expensive_compute(5)  # 0.1s
expensive_compute(5)  # 瞬间返回(缓存命中)

# 查看缓存信息
print(expensive_compute.cache_info())
# CacheInfo(hits=1, misses=1, maxsize=128, currsize=1)

# 清除缓存
expensive_compute.cache_clear()

权限检查

from functools import wraps

def require_auth(role="user"):
    def decorator(func):
        @wraps(func)
        def wrapper(user, *args, **kwargs):
            if not user.get("authenticated"):
                raise PermissionError("未认证")
            if user.get("role") != role and role != "user":
                raise PermissionError(f"需要 {role} 权限")
            return func(user, *args, **kwargs)
        return wrapper
    return decorator

@require_auth(role="admin")
def delete_user(current_user, user_id):
    print(f"删除用户 {user_id}")

admin = {"authenticated": True, "role": "admin"}
user = {"authenticated": True, "role": "user"}

delete_user(admin, 42)   # 删除用户 42
# delete_user(user, 42)  # PermissionError: 需要 admin 权限

日志记录

from functools import wraps
import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

def log_call(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        args_repr = [repr(a) for a in args]
        kwargs_repr = [f"{k}={v!r}" for k, v in kwargs.items()]
        signature = ", ".join(args_repr + kwargs_repr)
        logger.info(f"调用 {func.__name__}({signature})")
        try:
            result = func(*args, **kwargs)
            logger.info(f"{func.__name__} 返回 {result!r}")
            return result
        except Exception as e:
            logger.exception(f"{func.__name__} 抛出异常 {e}")
            raise
    return wrapper

@log_call
def divide(a, b):
    return a / b

divide(10, 3)
# INFO: 调用 divide(10, 3)
# INFO: divide 返回 3.3333333333333335

输入验证

from functools import wraps

def validate_types(**expected_types):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            # 获取函数参数名
            import inspect
            sig = inspect.signature(func)
            bound = sig.bind(*args, **kwargs)
            bound.apply_defaults()
            
            for param_name, expected_type in expected_types.items():
                if param_name in bound.arguments:
                    value = bound.arguments[param_name]
                    if not isinstance(value, expected_type):
                        raise TypeError(
                            f"参数 {param_name} 期望 {expected_type.__name__},"
                            f"实际得到 {type(value).__name__}"
                        )
            return func(*args, **kwargs)
        return wrapper
    return decorator

@validate_types(name=str, age=int)
def create_user(name, age):
    return {"name": name, "age": age}

create_user("Alice", 30)    # {'name': 'Alice', 'age': 30}
# create_user("Alice", "30")  # TypeError: 参数 age 期望 int,实际得到 str

本章小结:装饰器是 Python 最强大的语法特性之一,本质是利用函数作为一等公民的特性,对函数进行包装增强。核心要点:永远使用 @functools.wraps 保留元信息、理解多层装饰器的执行顺序、区分装饰函数和装饰类的场景。