随着 AI 项目规模的增长,代码量很快就会超出单个 Python 文件或 Notebook 的承载极限。为了防止代码变成“面条式代码 (Spaghetti Code)”,我们需要将复杂的系统拆分为结构清晰的模块 (Module) 与 包 (Package),并用规范的面向对象 (OOP) 和配置机制 (Configuration) 进行管理。
完成本节后,你应能:
import 语句的底层机制与 sys.path 模块搜索路径规则。__init__.py 的作用。Dataset 和模型基类 API。@dataclass 和 argparse 构建规范的 AI 超参数配置管理系统。if __name__ == '__main__' 的工程防污染价值。把你的 AI 项目看作一栋大楼:单个 .py 文件是集成了特定功能的“房间(模块)”,含有 init.py 的文件夹则是“楼层(包)”,而 sys.path 则是连接各个房间的“走廊与通道”。配置类(Config)则是楼层的控制蓝图,指导各个房间如何协同运行。
在 Python 中,任何以 .py 结尾的文件都是一个模块 (Module)。使用 import 语句可以将一个模块中的函数、类或变量引入到当前文件中。
当运行 import my_module 时,Python 按以下顺序在 sys.path 路径列表中寻找对应的文件:
PYTHONPATH 中指定的目录。site-packages)。import sys
import os
# 查看当前 Python 的模块搜索路径
print("当前 Python 搜索路径:")
for path_entry in sys.path[:3]: # 仅展示前3项
print(f" - {path_entry}")
# 动态添加自定义路径(当导入第三方未安装包时)
custom_dir = os.path.abspath("./custom_libs")
if custom_dir not in sys.path:
sys.path.append(custom_dir)
检查运行脚本同级目录
检查自定义环境变量路径
检查 Python 内置库与 pip 安装包
__init__.py包 (Package) 是包含 __init__.py 文件以及一个或多个模块的文件夹。__init__.py 的主要作用包括:
__all__ 变量控制 from package import * 时暴露的导出一览。假设我们有如下 AI 项目目录架构:
my_ai_project/
├── my_package/
│ ├── __init__.py
│ ├── data.py
│ └── model.py
└── main.py
在 my_package/__init__.py 中:
# 将内部子模块的重要类直接提升到包顶层,方便外部调用
from .data import DataLoader
from .model import SimpleNN
__all__ = ["DataLoader", "SimpleNN"]
外部 main.py 导入示例:
# 绝对导入(推荐)
from my_package.data import DataLoader
from my_package.model import SimpleNN
# 或者直接导入包顶层(依赖 __init__.py 的提升导出)
# from my_package import DataLoader, SimpleNN
在 AI 领域(特别是 PyTorch / Scikit-learn),绝大多数模型、数据集与训练器都是基于类 (Class) 构建的。
self__init__ 方法:构造函数,在实例化时自动触发。self:代表实例对象本身,用于访问实例属性与方法。AI 框架(如 PyTorch)通过继承机制规范代码模式。例如继承 Dataset 基类并重写 __len__ 和 __getitem__ 方法:
声明父类 __init__ 构造函数
声明 class ImageDataset(BaseDataset)
子类调用 super() 初始化父类属性
子类成功集成父类与子类的全部属性
在机器学习实验中,将模型超参数与训练代码解耦是保证实验可复现的关键。
@dataclass 定义结构化配置Python 3.7+ 引入的 dataclass 可以极大简化配置类的编写,无需手动编写 __init__ 代码:
argparse 解析命令行参数在终端运行训练脚本时,argparse 允许我们从命令行动态传入超参数:
尝试在下方交互框中编辑并直接点击运行,体验类继承、@dataclass 与命令行参数 argparse 的联动控制:
if __name__ == '__main__'当一个 Python 文件被执行或导入时,全局变量 __name__ 会被赋予不同的值:
__name__ 的值为 '__main__'。__name__ 的值为该模块的文件名。使用 if __name__ == '__main__': 守护块可以保证:作为模块被他人导入时,不会意外触发测试代码或训练脚本。
def train_pipeline():
print("[Pipeline] 开始执行训练流程...")
def helper_util():
return "工具函数辅助输出"
# 规范脚本主入口
if __name__ == "__main__":
print("当前文件作为主程序启动:")
train_pipeline()
在真实 AI 框架中,模块、包与配置的应用无处不在:
torch.nn.Module,并在 __init__ 中定义网络层,在 forward 中定义前向数据流。torch.utils.data.Dataset 实现重写。@dataclass 配合 argparse 或 yaml 配置文件,将其解包为 **dataclasses.asdict(config) 传给 WandB 或 TensorBoard 记录日志。| 错误现象 / 异常类型 | 常见原因 | 解决方案 |
|---|---|---|
ModuleNotFoundError: No module named 'xxx' | 模块所在的路径未包含在 sys.path 中,或包缺失 __init__.py | 检查启动路径,或用 sys.path.append() 补全搜索路径;确保文件夹存在 __init__.py |
AttributeError: module 'xxx' has no attribute 'yyy' | 循环导入 (Circular Import) 或文件名与标准库同名冲突 (如创建了 random.py) | 检查重命名同名文件;将互相依赖的 import 移动到函数内部延迟加载 |
TypeError: __init__() missing required argument | 实例化类对象时漏掉了未设默认值的必填形参 | 检查类的 __init__ 定义,按顺序补全参数或指定关键字参数 |
ImportError: cannot import name 'xxx' from partially initialized module | 两个模块之间存在循环引用 | 重新解耦代码结构,提取公共部分到第三个模块 |
import 查找 sys.path 的优先级逻辑。__init__.py 并理清包结构。self 和 __init__。@dataclass 创建简易参数配置类。argparse 定义和解析命令行超参数。if __name__ == '__main__': 保护。math.py 的文件,并在其中运行 import math; print(math.sqrt(4)),会发生什么?为什么?class A:
def show(self):
return "Class A"
class B(A):
def show(self):
return "Class B -> " + super().show()
print(B().show())
ModelConfig 的 @dataclass,包含 learning_rate(默认 0.01)、hidden_dim(默认 128)和 use_gpu(默认 True),并编写一个函数接收该配置并打印调试信息。