在这里插入图片描述


📖 开篇导读

在上一节课中,我们学习了迭代器和生成器,它们体现了惰性求值和函数式编程的思想。今天,我们将探索Python中另一个极其强大且优雅的特性——装饰器(Decorator)

装饰器是一种在不修改原函数代码的情况下,为函数添加额外功能的技术。它本质上是一个函数(或类),接收一个函数作为参数,返回一个新的函数。装饰器广泛应用于日志记录、性能计时、权限校验、缓存、事务处理等横切关注点(Aspect-Oriented Programming)。

💡 工作场景

  • Web框架中,@app.route('/')将函数注册为路由处理函数。
  • @login_required装饰器检查用户是否登录。
  • @cache装饰器缓存函数结果,提高性能。
  • @timer装饰器测量函数执行时间,用于性能分析。

本课将系统讲解:

  • 装饰器的本质(闭包的应用)
  • 无参装饰器的实现与使用
  • 带参数的装饰器(三层嵌套)
  • 多个装饰器叠加的执行顺序
  • functools.wraps保留函数元信息
  • 类装饰器
  • 实战案例:计时器、日志、重试、权限校验

学完本课,你将能够编写自己的装饰器,写出更简洁、更优雅的代码。


🎯 学习目标

目标编号 具体掌握内容 对应面试/工作价值
1️⃣ 理解装饰器的本质(闭包,函数作为参数和返回值) 面试必问“什么是装饰器”
2️⃣ 掌握无参装饰器的编写和应用 常见的日志、计时等场景
3️⃣ 掌握带参数的装饰器的实现(三层嵌套) 灵活的配置化装饰器
4️⃣ 理解多个装饰器叠加的执行顺序 调试装饰器链
5️⃣ 熟练使用functools.wraps保留函数元数据 避免调试困难
6️⃣ 了解类装饰器的实现方式 替代方案的灵活性

🔥 面试考点:“写一个计时器装饰器”“装饰器如何接收参数?”“多个装饰器的执行顺序?”“@wraps的作用?”


📚 知识点理论精讲

一、装饰器的本质:闭包

在学习装饰器之前,我们先复习闭包(第13课)。装饰器本质上是一个返回函数的函数,利用闭包“记住”被装饰的函数。

1.1 最简单的装饰器

def my_decorator(func):
    def wrapper():
        print("调用前")
        func()
        print("调用后")
    return wrapper

def say_hello():
    print("Hello")

say_hello = my_decorator(say_hello)
say_hello()

输出:

调用前
Hello
调用后

1.2 语法糖 @

Python提供了@语法糖,简化装饰器的使用:

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

等价于say_hello = my_decorator(say_hello)

1.3 装饰带参数的函数

装饰器内部的wrapper需要能够接收任意参数,通常使用*args, **kwargs

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

@logger
def add(a, b):
    return a + b

add(3, 5)

二、有参装饰器(装饰器工厂)

有时装饰器本身需要参数(例如指定日志级别、重试次数)。这需要再增加一层嵌套,总共三层

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

@repeat(3)
def greet(name):
    print(f"Hello {name}")

greet("张三")

执行流程:

  1. @repeat(3) 先调用repeat(3),返回decorator
  2. @decorator 装饰greet,等价于greet = decorator(greet)

三、多个装饰器叠加

多个装饰器作用于同一个函数时,执行顺序是从下往上(靠近函数的先执行)。

def decorator_a(func):
    def wrapper(*args, **kwargs):
        print("A before")
        result = func(*args, **kwargs)
        print("A after")
        return result
    return wrapper

def decorator_b(func):
    def wrapper(*args, **kwargs):
        print("B before")
        result = func(*args, **kwargs)
        print("B after")
        return result
    return wrapper

@decorator_a
@decorator_b
def hello():
    print("Hello")

hello()
# 输出顺序:
# A before
# B before
# Hello
# B after
# A after

记忆:最靠近函数的装饰器(@decorator_b)先执行其wrapper的“before”部分,然后进入外层装饰器。


四、保留函数元信息:functools.wraps

装饰器会覆盖被装饰函数的__name____doc__等属性,影响调试和文档生成。使用functools.wraps可以复制这些信息。

import functools

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

@timer
def slow_function():
    """这是一个慢函数"""
    import time
    time.sleep(1)

print(slow_function.__name__)  # 输出 slow_function(而非wrapper)
print(slow_function.__doc__)   # 输出文档字符串

始终使用@functools.wraps编写装饰器


五、类装饰器

除了函数,也可以使用类来实现装饰器。类需要实现__call__方法。

class CountCalls:
    def __init__(self, func):
        self.func = func
        self.count = 0
    
    def __call__(self, *args, **kwargs):
        self.count += 1
        print(f"调用次数: {self.count}")
        return self.func(*args, **kwargs)

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

say_hello()
say_hello()

类装饰器适合需要维护状态的场景(如计数器、缓存)。


六、常见装饰器实战

6.1 计时器

import time
import functools

def timer(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        end = time.perf_counter()
        print(f"{func.__name__} took {end-start:.6f}s")
        return result
    return wrapper

6.2 日志装饰器

def log(level="INFO"):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            print(f"[{level}] 调用 {func.__name__} 参数: {args} {kwargs}")
            return func(*args, **kwargs)
        return wrapper
    return decorator

6.3 重试装饰器

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

6.4 缓存(记忆化)

def cache(func):
    _cache = {}
    @functools.wraps(func)
    def wrapper(*args):
        if args in _cache:
            print(f"缓存命中: {args}")
            return _cache[args]
        result = func(*args)
        _cache[args] = result
        return result
    return wrapper

💻 代码案例实操

案例1:无参装饰器——性能计时器

"""
timer_decorator.py
实现一个通用的性能计时装饰器
"""

import time
import functools

def timer(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        end = time.perf_counter()
        elapsed = end - start
        print(f"{func.__name__} 执行耗时: {elapsed:.6f}秒")
        return result
    return wrapper

@timer
def slow_sum(n):
    total = 0
    for i in range(n):
        total += i
        time.sleep(0.00001)  # 模拟耗时
    return total

@timer
def fast_sum(n):
    return n * (n - 1) // 2

print(slow_sum(1000))
print(fast_sum(1000))

案例2:有参装饰器——带配置的日志

"""
log_decorator.py
带参数的日志装饰器,可指定输出级别和文件
"""

import functools
import sys

def log(level="INFO", output=sys.stdout):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            msg = f"[{level}] 调用 {func.__name__} args={args} kwargs={kwargs}"
            output.write(msg + "\n")
            result = func(*args, **kwargs)
            output.write(f"[{level}] {func.__name__} 返回 {result}\n")
            return result
        return wrapper
    return decorator

@log(level="DEBUG")
def add(a, b):
    return a + b

@log(level="ERROR", output=sys.stderr)
def divide(a, b):
    return a / b

add(3, 5)
divide(10, 2)

案例3:多个装饰器叠加——权限校验+日志

"""
multi_decorators.py
演示多个装饰器的执行顺序和组合使用
"""

import functools

def require_auth(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        # 模拟权限检查
        print("权限检查通过")
        return func(*args, **kwargs)
    return wrapper

def audit_log(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print(f"审计日志: 调用 {func.__name__}")
        return func(*args, **kwargs)
    return wrapper

@audit_log
@require_auth
def delete_user(user_id):
    print(f"删除用户 {user_id}")

delete_user(1001)
# 输出顺序:
# 审计日志: 调用 delete_user
# 权限检查通过
# 删除用户 1001

案例4:@wraps的重要性演示

"""
wraps_importance.py
演示不使用wraps的后果及使用wraps的好处
"""

import functools

def bad_decorator(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

def good_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

@bad_decorator
def my_func():
    """这是原函数文档"""
    pass

@good_decorator
def my_func2():
    """这是原函数文档"""
    pass

print("bad_decorator 结果:")
print("  __name__:", my_func.__name__)   # wrapper
print("  __doc__:", my_func.__doc__)     # None

print("good_decorator 结果:")
print("  __name__:", my_func2.__name__)  # my_func2
print("  __doc__:", my_func2.__doc__)    # 这是原函数文档

案例5:重试装饰器——处理临时失败

"""
retry_decorator.py
实现一个重试装饰器,支持指数退避
"""

import functools
import time
import random

def retry(max_attempts=3, delay=1, backoff=2, exceptions=(Exception,)):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            current_delay = delay
            for attempt in range(1, max_attempts + 1):
                try:
                    return func(*args, **kwargs)
                except exceptions as e:
                    print(f"尝试 {attempt}/{max_attempts} 失败: {e}")
                    if attempt == max_attempts:
                        raise
                    time.sleep(current_delay)
                    current_delay *= backoff
            return None
        return wrapper
    return decorator

@retry(max_attempts=5, delay=0.5, exceptions=(ValueError,))
def unstable_function():
    if random.random() < 0.7:
        raise ValueError("随机失败")
    return "成功"

try:
    result = unstable_function()
    print(f"最终结果: {result}")
except ValueError as e:
    print(f"重试全部失败: {e}")

案例6:缓存装饰器(LRU简易版)

"""
cache_decorator.py
实现一个简单的缓存装饰器(类似functools.lru_cache)
"""

import functools

def cache(maxsize=128):
    def decorator(func):
        cache_dict = {}
        cache_list = []  # 用于记录访问顺序(简化)
        
        @functools.wraps(func)
        def wrapper(*args):
            # 注意:只处理位置参数,且要求可哈希
            key = args
            if key in cache_dict:
                print(f"缓存命中: {key}")
                # 移到最新(简化版省略)
                return cache_dict[key]
            result = func(*args)
            if len(cache_dict) >= maxsize:
                # 删除最早的一个(模拟LRU,这里简化删除第一个)
                oldest = cache_list.pop(0)
                del cache_dict[oldest]
            cache_dict[key] = result
            cache_list.append(key)
            print(f"计算并缓存: {key}")
            return result
        return wrapper
    return decorator

@cache(maxsize=3)
def fib(n):
    if n < 2:
        return n
    return fib(n-1) + fib(n-2)

print(fib(10))  # 会大量缓存命中,加速计算

案例7:类装饰器——函数调用计数器

"""
class_decorator.py
用类装饰器实现调用计数和重置功能
"""

import functools

class CountCalls:
    def __init__(self, func):
        functools.update_wrapper(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)
    
    def reset(self):
        self.count = 0
        print(f"计数器已重置")

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

say_hello()
say_hello()
say_hello.reset()
say_hello()

案例8:综合实战——带参数的权限装饰器

"""
permission_decorator.py
实现基于角色的权限控制装饰器
"""

import functools

# 模拟用户上下文(全局变量,实际应从请求中获取)
current_user = {"name": "张三", "roles": ["user"]}

def require_permission(required_role):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            if required_role not in current_user["roles"]:
                raise PermissionError(f"用户 {current_user['name']} 没有 {required_role} 权限")
            print(f"权限验证通过: {required_role}")
            return func(*args, **kwargs)
        return wrapper
    return decorator

def login_required(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        if not current_user.get("name"):
            raise PermissionError("未登录")
        return func(*args, **kwargs)
    return wrapper

@login_required
@require_permission("admin")
def delete_user(user_id):
    print(f"删除用户 {user_id}")

# 测试
try:
    delete_user(100)
except PermissionError as e:
    print(e)  # 无admin权限

# 提升权限
current_user["roles"].append("admin")
delete_user(100)  # 成功

⚠️ 易错点避坑总结

序号 坑点描述 后果 解决方案
1 装饰器内部函数忘记return func(*args, **kwargs) 原函数的返回值丢失,返回None 在wrapper中return原函数调用结果
2 不带@functools.wraps 函数元信息(name, doc)丢失,调试困难 始终使用@functools.wraps
3 有参装饰器忘记三层嵌套 装饰器调用时出错 记住:有参装饰器 = 外层函数接收参数,返回内层装饰器
4 多个装饰器顺序错误 功能执行顺序与预期不符 靠近函数的装饰器先执行(before部分)
5 在装饰器中修改可变默认参数 经典陷阱,可能累积状态 避免在装饰器中使用可变默认参数
6 装饰器不处理关键字参数 被装饰函数使用关键字参数时出错 wrapper使用*args, **kwargs
7 类装饰器中忘记调用functools.update_wrapper 丢失元信息 __init__中调用update_wrapper
8 装饰器抛出异常时,原函数未执行 逻辑错误 根据需求决定是否要try-except
9 在装饰器内部定义变量与闭包变量重名 意外覆盖 注意变量命名
10 对类方法使用装饰器时忘记处理self 参数错位 wrapper需要(*args, **kwargs),会正常传递self

📝 课后实战练习题

第1题:实现@timeit装饰器

编写装饰器@timeit,打印函数执行时间(单位毫秒),精确到小数点后3位。要求使用functools.wraps

第2题:实现@deprecated装饰器

当调用被装饰的函数时,打印警告信息“函数xxx已弃用,将在未来版本移除”。可以传入替代函数的名称。

第3题:实现@retry装饰器(指数退避)

扩展重试装饰器,支持max_attemptsinitial_delaybackoff_factor,并且可以指定需要重试的异常类型列表。

第4题:实现@singleton装饰器

使用类装饰器或函数装饰器,确保被装饰的类只能创建一个实例(单例模式)。测试多次创建得到同一对象。

第5题:实现@validate装饰器

装饰器接收参数类型声明,如@validate(int, str),检查函数参数的类型,不符合则抛出TypeError

第6题:实现@cache装饰器(支持maxsize)

类似functools.lru_cache,缓存最近使用的maxsize个结果。使用有序字典(collections.OrderedDict)实现LRU。

第7题:综合应用——Web路由模拟

实现一个简易的路由装饰器@route(path),将函数注册到路由表中。然后模拟请求路径,调用对应函数。


🧠 知识点思维导图总结

第32课:装饰器

本质

闭包

函数作为参数和返回值

语法糖@

无参装饰器

基本结构

wrapper接收*args, **kwargs

返回原函数结果

有参装饰器

三层嵌套

外层接收参数,返回装饰器

多个装饰器

叠加语法

执行顺序:靠近函数先执行其before部分

保留元信息

@functools.wraps

name, __doc__等

类装饰器

实现__call__

适合有状态场景

常见应用

计时器

日志

重试

缓存

权限校验

面试考点

手写计时器

带参数的装饰器

多个装饰器顺序

wraps的作用


🔜 下节课预告

装饰器是函数式编程的高级应用。下一节课我们将进入并发编程领域,学习多线程编程基础。

第33课:并发编程基础:多线程创建、生命周期与线程安全详解

内容包括:

  • 进程与线程的概念
  • threading模块创建线程
  • 线程生命周期与守护线程
  • 线程同步(Lock、RLock、Condition)
  • 线程安全与GIL
  • 实战:多线程下载、生产者消费者模式

并发编程是提升程序性能的重要手段,掌握它你就能编写高效的多任务程序。

🌟 学习鼓励:装饰器是Python的语法亮点,它不仅让代码更简洁,也体现了函数式编程的美妙。初学时可能会对多层嵌套感到困惑,但请多动手写,尤其是带参数的装饰器。坚持练习,你会发现装饰器是提升代码复用性和可读性的利器。


🔗《50节课 Python 从入门到精通》系列课程导航

去订阅

🌟 感谢您耐心阅读到这里!
💡 如果本文对您有所启发欢迎:
👍 点赞📌 收藏 📤 分享给更多需要的伙伴。
🗣️ 期待在评论区看到您的想法, 共同进步。
🔔 关注我,持续获取更多干货内容~
🤗 我们下篇文章见~

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐