03. Python OOP and Utility Patterns | Python 面向对象与工具模式
难度: Easy | 环境: CPU-first | 标签: Python, 对象封装, 工具模式 | 目标人群: Part 2-4 前置补课者
🚀 云端运行环境
本章节的实战代码可以点击以下链接在免费 GPU 算力平台上直接运行:
本页聚焦:会把配置和状态收进对象;会给对象补最小方法,让结果能直接看见;会判断什么时候该用 helper,什么时候该用 class。这里先把对象封装当成后面读 nn.Module 的前置练习:先看配置怎么收,再看方法怎么挂。
关键词: class, dataclass, config
前置阅读
导语: 先看 0A 组页,把 Python 基础和对象封装的边界对齐,再进入这一页会更顺。
相关阅读
导语: 本页先把对象封装和工具模式的最小判断讲清楚;如果想继续把配置和 I/O 的写法补完整,可以顺着看下面这些页。
Q1:什么时候该把配置收成对象?
如果一组字段会反复被读取、覆盖、打印或传递,通常就不该继续散在多个变量里。把它们收进对象后,默认值、更新方式和输出格式都会更稳定。对 Part 2 来说,这一步就是在提前练“对象边界怎么定”,后面看 nn.Module 时会更顺。
from dataclasses import dataclass
@dataclass
class RunConfig:
lr: float = 1e-3
batch_size: int = 32
epochs: int = 3
def as_dict(self):
return {
'lr': self.lr,
'batch_size': self.batch_size,
'epochs': self.epochs,
}
config = RunConfig()
print('RunConfig 默认值:', config.as_dict())Q1验证:默认值和覆盖是否稳定?
这里直接检查两件事:默认值是否存在,修改字段后 as_dict() 是否还能正确反映更新后的状态。你要记住的是,配置对象化的核心不是写法,而是“状态有没有被稳定收起来”。
config = RunConfig()
assert config.lr == 1e-3
assert config.batch_size == 32
assert config.as_dict() == {'lr': 1e-3, 'batch_size': 32, 'epochs': 3}
config.batch_size = 64
print('覆盖后的配置:', config.as_dict())
assert config.as_dict()['batch_size'] == 64
print('✅ RunConfig 通过')Q2:什么时候该给对象补最小方法?
当对象不仅要存数据,还要统一输出、汇总或格式化时,给它补一个最小方法通常比到处写重复的打印代码更稳。这里可以把它类比成后面模型对象里的 forward():对象不只是存状态,也要有统一行为。
@dataclass
class Metrics:
loss: float = 0.0
accuracy: float = 0.0
def to_dict(self):
return {'loss': self.loss, 'accuracy': self.accuracy}
def summary(self):
return f'loss={self.loss:.4f}, accuracy={self.accuracy:.2%}'
metrics = Metrics(loss=0.1234, accuracy=0.987)
print('Metrics 字典:', metrics.to_dict())
print('Metrics 摘要:', metrics.summary())Q2验证:对象输出是否统一?
这里要看的不是类定义得多不多,而是同一份状态能不能稳定输出成字典和摘要,方便后面日志、训练记录和实验汇总直接复用。对 Part 2 来说,这种“统一输出”会直接对应到模型状态和训练日志的管理方式。
metrics = Metrics(loss=0.2345, accuracy=0.9123)
assert metrics.to_dict() == {'loss': 0.2345, 'accuracy': 0.9123}
assert metrics.summary() == 'loss=0.2345, accuracy=91.23%'
print('✅ Metrics 通过')Q3:什么时候该把一次性 helper 收回 class?
如果函数开始反复传同一组状态、重复拼接输出、重复维护默认值,它通常已经接近对象边界了;这时把状态和方法收进 class 会更稳。换到 Part 2 里,这个判断很像“一个 helper 什么时候该变成模型对象的一部分”。
from dataclasses import dataclass
def format_run_tag(name, epochs):
return f'{name}@{epochs}'
@dataclass
class RunBuilder:
name: str
epochs: int
def summary(self):
return f'{self.name}@{self.epochs}|steps={self.epochs}'
tag = format_run_tag('TinyLM', 3)
builder = RunBuilder(name='TinyLM', epochs=3)
print('helper:', tag)
print('builder:', builder.summary())
assert tag == 'TinyLM@3'
assert builder.summary() == 'TinyLM@3|steps=3'
print('✅ helper_to_class 通过')