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 基础特训即将收尾。下一阶段,我们将把这些积木搭起来,去解决更复杂的问题!

相关推荐
无心水39 分钟前
【分布式利器:腾讯TSF】10、TSF故障排查与架构评审实战:Java架构师从救火到防火的生产哲学
java·人工智能·分布式·架构·限流·分布式利器·腾讯tsf
我的xiaodoujiao2 小时前
使用 Python 语言 从 0 到 1 搭建完整 Web UI自动化测试学习系列 38--Allure 测试报告
python·学习·测试工具·pytest
小鸡吃米…7 小时前
机器学习 - K - 中心聚类
人工智能·机器学习·聚类
好奇龙猫8 小时前
【AI学习-comfyUI学习-第三十节-第三十一节-FLUX-SD放大工作流+FLUX图生图工作流-各个部分学习】
人工智能·学习
沈浩(种子思维作者)8 小时前
真的能精准医疗吗?癌症能提前发现吗?
人工智能·python·网络安全·健康医疗·量子计算
minhuan8 小时前
大模型应用:大模型越大越好?模型参数量与效果的边际效益分析.51
人工智能·大模型参数评估·边际效益分析·大模型参数选择
Cherry的跨界思维8 小时前
28、AI测试环境搭建与全栈工具实战:从本地到云平台的完整指南
java·人工智能·vue3·ai测试·ai全栈·测试全栈·ai测试全栈
MM_MS8 小时前
Halcon变量控制类型、数据类型转换、字符串格式化、元组操作
开发语言·人工智能·深度学习·算法·目标检测·计算机视觉·视觉检测
ASF1231415sd8 小时前
【基于YOLOv10n-CSP-PTB的大豆花朵检测与识别系统详解】
人工智能·yolo·目标跟踪
njsgcs9 小时前
ue python二次开发启动教程+ 导入fbx到指定文件夹
开发语言·python·unreal engine·ue