随着程序功能的不断丰富,如果把成百上千行代码全部挤在单个 .py 文件中,代码的可读性和维护性会迅速恶化。
我们如何将代码按照功能拆分到不同的文件中?当运行 import 导入外部代码时,Python 是如何寻找到对应文件的?为什么有时导入自定义模块会提示 ModuleNotFoundError?
同时,在 AI 编程(如 PyTorch、Scikit-learn)中,模型、数据集和评估器大多基于面向对象编程 (OOP) 构建。如何使用类 (Class)、继承 (Inheritance) 和 方法重写 (Overriding) 来组织解耦的代码?
本章将带你深入理解 模块与包的底层机制,并掌握 面向对象编程 (OOP) 的核心精髓。
完成本章学习后,你将能够:
__init__.py 的三大作用;sys.path 模块查找机制与绝对/相对导入规范;math、random、datetime 和 os 内置标准库;if __name__ == '__main__': 脚本入口保护机制;self 的底层逻辑;Inheritance)、super() 函数与方法重写 (Overriding)。sys.path 查找机制.py 文件就是一个模块(如 data_loader.py);__init__.py 文件以及一个或多个模块的文件夹(用于组织大型项目)。my_ai_project/ <- 项目根目录
├── my_package/ <- Python 包 (Package)
│ ├── __init__.py <- 包标识与导出控制文件
│ ├── data_loader.py <- 子模块 A
│ └── model.py <- 子模块 B
└── main.py <- 主运行脚本
__init__.py 的三大核心作用import 的包”;import my_package 时,__init__.py 中的代码会自动率先执行;__all__):通过在 __init__.py 中提升子模块类,让外部调用更方便。# file: my_package/__init__.py
# 将子模块中的类提升到包顶层
from .data_loader import DataLoader
from .model import SimpleModel
# 控制使用 from my_package import * 时对外暴露的类
__all__ = ["DataLoader", "SimpleModel"]
外部 main.py 调用方式对比:
# 未在 __init__.py 中提升时的繁琐写法:
# from my_package.data_loader import DataLoader
# 提升后的优雅写法:
from my_package import DataLoader, SimpleModel
sys.path 模块查找路径当你执行 import my_module 时,Python 并不是在全盘搜索文件,而是严格按照列表 sys.path 中记录的目录顺序依次寻找:
检查运行脚本同级目录
检查自定义环境变量路径
检查 Python 内置库与 pip 安装包
import sys
# 查看当前 Python 的模块查找路径列表
print("Python 路径查找顺序:")
for path in sys.path[:3]: # 展示前 3 个优先目录
print(f" - {path}")
# 解决 ModuleNotFoundError 的应急方案:动态添加路径
# sys.path.append("/path/to/custom_directory")
Python 内置了丰富的标准库,无需额外 pip install 即可直接 import:
| 模块名 | 核心功能 | 高频方法/属性 | 代码示例 |
|---|---|---|---|
math | 数学计算 | sqrt() (开方), ceil() (上取整), floor() (下取整) | math.sqrt(16) |
random | 随机数与抽样 | randint(a,b) (随机整数), choice(seq) (随机选一), shuffle() (打乱) | random.choice(["A", "B"]) |
datetime | 日期时间处理 | datetime.now() (当前时间), strftime() (时间格式化) | datetime.now().strftime("%Y-%m-%d") |
os | 操作系统文件路径 | os.path.exists() (检查路径), os.mkdir() (创建文件夹) | os.path.exists("./data") |
if __name__ == '__main__':当你编写一个 .py 脚本时,它既可以被直接运行,也可能被其他文件 import 导入。
__name__ 变量赋值为 '__main__';import 导入时,该文件的 __name__ 变量等于模块名本身。# file: math_utils.py
def add(a: float, b: float) -> float:
return a + b
# 测试代码入口保护
if __name__ == '__main__':
print("正在直接运行 math_utils.py 脚本进行功能自测:")
print(f"2 + 3 = {add(2, 3)}")
面向对象编程 (OOP) 是一种将**数据(属性)与针对数据的操作(方法)**打包封装在同一个逻辑单元中的编程思想。
class 关键字、__init__ 与 selfclass:定义类的关键字,类名习惯采用大驼峰命名法(如 DataLoader);__init__() 构造函数:根据类创建新对象时自动调用的初始化方法;self 参数:代表当前被创建或调用的对象实例本身。必须作为类内部实例方法的第一个形参。class Student:
"""学生类定义"""
# 构造函数:初始化对象的属性
def __init__(self, name: str, score: float):
self.name = name # 绑定实例属性 name
self.score = score # 绑定实例属性 score
# 实例方法:第一个参数必须是 self
def print_info(self):
print(f"学生姓名: {self.name}, 成绩: {self.score}")
在复杂的 AI 框架(如 PyTorch、Scikit-learn)中,继承 (Inheritance) 和 方法重写 (Overriding) 是最核心的代码复用机制。
子类可以自动拥有父类(基类)的所有属性和方法,从而避免编写重复代码。
语法格式:
class 子类名(父类名):
# 子类代码
super() 函数与父类初始化在子类的 __init__ 方法中,使用 super().__init__() 可以自动调用父类的构造函数,继承父类的初始化逻辑。
如果父类定义的方法不能满足子类的需求,子类可以重新编写同名方法进行覆盖。
# 1. 父类(基类):通用模型类
class BaseModel:
def __init__(self, model_name: str):
self.model_name = model_name
def predict(self, x: float) -> float:
"""父类的通用预测接口,要求子类重写"""
print("警告:基类未实现具体的预测逻辑!")
return 0.0
# 2. 子类:继承 BaseModel 并重写 predict 方法
class LinearModel(BaseModel):
def __init__(self, model_name: str, weight: float, bias: float):
# 使用 super() 调用父类的 __init__ 构造函数
super().__init__(model_name)
self.weight = weight
self.bias = bias
# 重写父类的 predict 方法
def predict(self, x: float) -> float:
return self.weight * x + self.bias
# 实例化子类对象
model = LinearModel(model_name="线性回归模型", weight=2.0, bias=1.0)
print(f"模型名称: {model.model_name}") # 继承自父类的属性
print(f"预测结果 y = 2.0 * 3 + 1.0 = {model.predict(3.0)}") # 重写后的方法
请在下方的代码块中,点击 “运行代码”,体验基于继承、super() 和方法重写构建的数据处理类:
| 错误现象 / 异常类型 | 常见原因 | 标准排查与处理方式 |
|---|---|---|
ModuleNotFoundError: No module named 'xxx' | 模块文件不在 sys.path 搜索路径列表中 | 检查拼写,或使用 sys.path.append() 将自定义路径引入 |
AttributeError: module 'xxx' has no attribute 'yyy' | 自定义 .py 脚本的文件名与系统标准库名字相同(如自建了 random.py) | 绝对不要将自己的脚本命名为 random.py 或 math.py 等标准库名字 |
TypeError: method() takes 1 positional argument but 2 were given | 在类的方法定义中漏写了首个形参 self | 在类的方法中必须显式将 self 作为第一个参数 |
NotImplementedError | 调用了子类中尚未重写的基类抽象方法 | 检查子类代码,重写父类中定义的接口方法 |
__init__.py 的三大作用;sys.path 模块查找机制与 ModuleNotFoundError 的排查方案;math、random、datetime 和 os 标准库使用;if __name__ == '__main__': 语句的入口测试保护逻辑;__init__ 与 self;class Sub(Parent): 实现继承,并使用 super() 和重写方法。共 4 道精选测试题 · 答题进度已自动保存
恭喜你掌握了 Python 模块包管理与面向对象继承机制!
下一步我们将深入学习 07. 文件读写与调试技巧,掌握文本与 JSON 文件读写、with 上下文管理器,以及 try-except 异常捕获排错技能。