ADVANCE Day27

@浙大疏锦行

📘 Day 27 实战作业:函数魔法 ------ 装饰器 (Decorators)

1. 作业综述

核心目标

掌握装饰器(Decorator)的写法与应用。装饰器本质上是一个**"包装器"**,它允许你在不修改原函数代码的情况下,给函数动态地"增加功能"(如计时、日志、权限检查)。

涉及知识点

  • 闭包 (Closure):函数内部定义函数。
  • 语法糖 (Syntactic Sugar)@decorator_name 的写法。
  • 通用包装 :使用 *args**kwargs 传递任意参数。

场景类比

如果函数是"手机",装饰器就是"手机壳"。

  • 手机壳可以防摔(增加鲁棒性)。
  • 手机壳可以带支架(增加功能)。
  • 最重要的是:装手机壳不需要拆开手机(不修改原代码)。

步骤 1:手动打造第一个装饰器

场景描述

我们在调试代码时,经常需要在函数执行前后打印 "开始执行..." 和 "执行结束"。

如果每个函数都手写这两行,代码会很乱。我们写一个 @simple_logger 装饰器来自动完成这件事。

任务

  1. 编写一个装饰器 simple_logger
  2. 它需要在目标函数执行前打印 ">>> [Start]",执行后打印 "<<< [End]"。
  3. 用它装饰一个简单的加法函数 add(a, b) 并运行。
python 复制代码
# 1. 定义装饰器函数
def simple_logger(func):
    """
    一个简单的日志装饰器
    func: 被装饰的目标函数
    """
    # 定义内部包装函数 (Wrapper)
    # *args, **kwargs 保证了无论 func 有多少参数都能传进去
    def wrapper(*args, **kwargs):
        print(f">>> [Start] 正在调用函数: {func.__name__}")
        
        # --- 真正执行原函数的地方 ---
        result = func(*args, **kwargs)
        # -------------------------
        
        print(f"<<< [End] 函数 {func.__name__} 执行完毕")
        return result
    
    # 返回包装好的新函数
    return wrapper

# 2. 使用装饰器 (语法糖 @)
@simple_logger
def add(a, b):
    print(f"  正在计算 {a} + {b} ...")
    return a + b

# 3. 测试
# 当你调用 add 时,实际上是在调用 wrapper
res = add(10, 20)
print(f"计算结果: {res}")
复制代码
>>> [Start] 正在调用函数: add
  正在计算 10 + 20 ...
<<< [End] 函数 add 执行完毕
计算结果: 30

步骤 2:实战应用 ------ 性能计时器

场景描述

在深度学习中,我们经常需要知道"数据加载花了多久"或者"模型训练一轮花了多久"。

重复写 start = time.time()end = time.time() 很麻烦。

让我们造一个 @timer 装饰器,哪里需要计就在哪里贴一个。

任务

  1. 编写 @timer 装饰器,计算函数执行耗时。
  2. 保留原函数的文档字符串 (functools.wraps),这是一个好习惯。
  3. 模拟一个耗时任务(使用 time.sleep)并测试。
python 复制代码
import time
from functools import wraps

def timer(func):
    @wraps(func) # 好习惯:保留原函数的元数据(函数名、注释等)
    def wrapper(*args, **kwargs):
        start_time = time.time() # 记录开始时间
        
        result = func(*args, **kwargs) # 执行原函数
        
        end_time = time.time() # 记录结束时间
        duration = end_time - start_time
        print(f"⏱️ 函数 [{func.__name__}] 耗时: {duration:.4f} 秒")
        return result
    return wrapper

# --- 测试计时器 ---

@timer
def heavy_computation():
    """模拟一个耗时的计算任务"""
    print("  开始搬砖 (sleep 1.5s)...")
    time.sleep(1.5)
    return "Done"

@timer
def process_data(n):
    """模拟处理 n 条数据"""
    print(f"  开始处理 {n} 条数据 (sleep 0.5s)...")
    time.sleep(0.5)

# 调用
print(f"任务返回值: {heavy_computation()}")
process_data(1000)

# 验证 functools.wraps 的作用
print(f"\n函数说明文档: {heavy_computation.__doc__}")
复制代码
  开始搬砖 (sleep 1.5s)...
⏱️ 函数 [heavy_computation] 耗时: 1.5001 秒
任务返回值: Done
  开始处理 1000 条数据 (sleep 0.5s)...
⏱️ 函数 [process_data] 耗时: 0.5028 秒

函数说明文档: 模拟一个耗时的计算任务

步骤 3:高级应用 ------ 自动重试机制 (Robustness)

场景描述

在爬虫或下载数据集时,网络可能会抖动。我们希望函数失败时能自动重试 几次,而不是直接报错崩溃。

这是一个非常经典的装饰器应用场景。

任务

编写一个带参数的装饰器逻辑(为了简化,我们这里直接硬编码重试 3 次),如果函数抛出异常,就捕获并重试,直到成功或达到最大次数。

python 复制代码
import random

def retry_3_times(func):
    """
    如果函数报错,最多重试 3 次
    """
    @wraps(func)
    def wrapper(*args, **kwargs):
        max_retries = 3
        for i in range(max_retries):
            try:
                print(f"🔄 第 {i+1} 次尝试执行...")
                return func(*args, **kwargs) # 尝试执行
            
            except Exception as e:
                print(f"❌ 第 {i+1} 次失败: {e}")
                if i == max_retries - 1: # 如果是最后一次
                    print("🚫 达到最大重试次数,放弃治疗。")
                    raise e # 抛出异常
    return wrapper

# --- 模拟不稳定的网络请求 ---
@retry_3_times
def unstable_network_request(url):
    # 模拟 70% 的概率失败
    if random.random() < 0.7:
        raise ConnectionError("连接超时")
    print(f"✅ 成功连接到 {url}")
    return 200

# 测试 (多运行几次看看效果)
print("--- 开始测试自动重试 ---")
try:
    unstable_network_request("www.google.com")
except Exception:
    print("程序最终报错停止。")
复制代码
--- 开始测试自动重试 ---
🔄 第 1 次尝试执行...
❌ 第 1 次失败: 连接超时
🔄 第 2 次尝试执行...
❌ 第 2 次失败: 连接超时
🔄 第 3 次尝试执行...
❌ 第 3 次失败: 连接超时
🚫 达到最大重试次数,放弃治疗。
程序最终报错停止。

🎓 Day 27 总结:代码的"插件化"思维

今天我们掌握了 Python 的 装饰器

不要被它的语法吓到,它本质上就是:把一个函数扔进另一个函数里加工一下再拿出来

装饰器的核心价值

  1. 非侵入式 :不想改动 train() 函数内部复杂的逻辑,但又想加个计时功能?加个 @timer 就行。
  2. 复用性 :写一个 @retry,可以用在下载函数、数据库连接函数、API 请求函数等任何地方。

深度学习伏笔

在 PyTorch 中,你会见到 @staticmethod (静态方法)、@property (属性化) 甚至自定义的 Hooks。理解了今天的作业,你再看那些源码时,看到的就不再是"天书",而是一个个精巧的Wrapper

Next Level: 我们的 Python 基础特训即将收尾。下一阶段,我们将把这些积木搭起来,去解决更复杂的问题!

相关推荐
安思派Anspire2 小时前
AI智能体:完整课程(高级)
人工智能
北邮刘老师2 小时前
马斯克的梦想与棋盘:空天地一体的智能体互联网
数据库·人工智能·架构·大模型·智能体·智能体互联网
AI码上来2 小时前
小智AI 如何自定义唤醒词+背景图:原理+流程拆解
人工智能
多则惑少则明2 小时前
AI大模型实用(八)Java快速实现智能体整理(使用LangChain4j-agentic来进行情感分析/分类)
java·人工智能·spring ai·langchain4j
m0_692457102 小时前
ROI切割-感兴趣区域
人工智能·深度学习·计算机视觉
吴佳浩 Alben2 小时前
Python入门指南(六) - 搭建你的第一个YOLO检测API
开发语言·python·yolo
love530love2 小时前
Win11+RTX3090 亲测 · ComfyUI Hunyuan3D 全程实录 ③:diso 源码编译实战(CUDA 13.1 零降级)
开发语言·人工智能·windows·python·comfyui·hunyuan3d·diso
落羽的落羽2 小时前
【C++】深入浅出“图”——图的遍历与最小生成树算法
linux·服务器·c++·人工智能·算法·机器学习·深度优先
BoBoZz192 小时前
WarpTo 对 3D 几何体进行形变(Warping操作,使其顶点朝着一个指定的空间点移动
python·vtk·图形渲染·图形处理