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

相关推荐
安全二次方security²1 天前
CUDA C++编程指南(7.25)——C++语言扩展之DPX
c++·人工智能·nvidia·cuda·dpx·cuda c++编程指南
童话名剑1 天前
训练词嵌入(吴恩达深度学习笔记)
人工智能·深度学习·word2vec·词嵌入·负采样·嵌入矩阵·glove算法
桂花很香,旭很美1 天前
智能体技术架构:从分类、选型到落地
人工智能·架构
HelloWorld__来都来了1 天前
2026.1.30 本周学术科研热点TOP5
人工智能·科研
共享家95271 天前
搭建 AI 聊天机器人:”我的人生我做主“
前端·javascript·css·python·pycharm·html·状态模式
aihuangwu1 天前
豆包图表怎么导出
人工智能·ai·deepseek·ds随心转
Hgfdsaqwr1 天前
Python在2024年的主要趋势与发展方向
jvm·数据库·python
YMWM_1 天前
深度学习中模型的推理和训练
人工智能·深度学习
中二病码农不会遇见C++学姐1 天前
文明6-mod制作-游戏素材AI生成记录
人工智能·游戏
一晌小贪欢1 天前
Python 测试利器:使用 pytest 高效编写和管理单元测试
python·单元测试·pytest·python3·python测试