Python 语法篇 · 第8章

模块与包:组织大型代码

当代码越来越多,就需要拆分文件、复用他人成果。本章讲透 import 机制、包结构、第三方库管理与工程目录组织。

🎯 学习目标

  • 理解模块(.py 文件)与包(文件夹)的概念
  • 掌握 import / from...import / as 三种导入方式与区别
  • 理解导入的本质、sys.path 搜索顺序与 __pycache__
  • 理解 if __name__ == "__main__" 的作用
  • 会创建自己的包,理解 __init__.py 与相对导入
  • 能用 pip + requirements.txt 管理项目依赖,组织标准工程目录

1什么是模块

一个 .py 文件就是一个模块(module)。把相关的函数、类、变量放进不同文件,可以:避免单文件过长、避免命名冲突、复用代码、团队分工。

# 假设文件结构
my_project/
├── main.py        # 主程序
└── calc.py        # 自定义计算模块
# ===== calc.py 的内容 =====
PI = 3.14159

def add(a, b):
    return a + b

def multiply(a, b):
    return a * b

2三种导入方式

方式一:import 模块名

# ===== main.py =====
import calc                    # 导入同目录下的 calc.py

print(calc.add(1, 2))          # 3    通过"模块名.功能"访问
print(calc.PI)                 # 3.14159

import os, sys                 # 可一次导入多个(PEP8 仍建议分行)

方式二:from 模块 import 具体功能

from calc import add, PI       # 只导入需要的名字
print(add(2, 3))               # 5  直接使用,不用加模块前缀
print(PI)

from calc import *             # 导入所有公开名字(不推荐:污染命名空间、来源不清)

方式三:as 起别名

import numpy as np             # 社区约定俗成的别名
import pandas as pd
import matplotlib.pyplot as plt

from calc import multiply as mul
print(mul(2, 3))               # 6
写法调用形式适用场景
import modmod.x最稳妥,来源清晰,推荐默认
from mod import xx高频使用的少数名字
import mod as mm.x名字过长或社区约定(np/pd/plt)

3导入的本质与搜索路径

导入一个模块,本质是把该 .py 文件从头到尾执行一遍,然后把其中定义的名字绑定到当前命名空间。同一个模块被多次 import 也只会执行一次(第二次直接取缓存)。

import sys
# sys.path 是模块搜索路径列表,import 时按顺序查找:
for p in sys.path:
    print(p)
# 依次包括:
# 1. 当前脚本所在目录(或交互模式的 cwd)
# 2. 环境变量 PYTHONPATH 中的目录
# 3. Python 安装目录与 site-packages(第三方库安装位置)

新手常见错误:自己的文件起名叫 random.py、string.py、os.py,结果 import random 导入了自己的空文件,标准库反而被遮蔽(shadow)。不要用标准库/第三方库的名字命名自己的文件。

导入模块后,Python 会在模块同目录生成 __pycache__/xxx.cpython-312.pyc 缓存文件(编译后的字节码),用于加速下次导入,可安全删除。

4name == "main" 的作用

模块被直接运行时,解释器把其 __name__ 设为 "__main__";模块被导入时,__name__ 是模块文件名。利用这一区别可以写"自测代码":

# ===== calc.py =====
def add(a, b):
    return a + b

# 下面的测试代码只在【直接运行 calc.py】时执行,被 import 时不执行
if __name__ == "__main__":
    print("自测:", add(2, 3))       # 直接运行 → 输出 5
    assert add(2, 3) == 5           # 简单断言测试
# ===== main.py =====
import calc        # 导入时不会触发 calc.py 里的测试输出
print(calc.add(10, 20))

这是每个可运行脚本的标准"主入口"写法,类似 C/Java 的 main 函数。建议:所有不希望被 import 时执行的启动逻辑,都放进 if __name__ == "__main__": 块。

5包(package):放模块的文件夹

包就是包含若干模块的文件夹,用来给模块分层归类。Python 3.3+ 的包可以没有 __init__.py(命名空间包),但传统/工程实践中仍建议创建它作为包标志。

my_project/
└── mypackage/
    ├── __init__.py        # 包初始化文件(可为空)
    ├── calc.py
    ├── text_utils.py
    └── nlp/               # 子包(可以层层嵌套)
        ├── __init__.py
        └── tokenizer.py
# 从项目根目录启动 main.py,用"包.模块"的路径导入
from mypackage.calc import add
import mypackage.nlp.tokenizer as tk

# __init__.py 的作用:
# 1) 标志这是一个 Python 包
# 2) 包被导入时会先执行它,可做初始化
# 3) 可在这里暴露对外接口,简化导入路径

# 例如在 mypackage/__init__.py 中写:
# from .calc import add, multiply
# 之后外部即可:from mypackage import add

相对导入

# 在 mypackage/nlp/tokenizer.py 内部,引用同包/上层模块:
from . import calc_mod          # .  当前包
from .. import calc             # .. 上一级包
from ..text_utils import clean  # ..模块名

# 注意:相对导入只能在【被当作包模块导入】时使用,
# 直接 python tokenizer.py 运行该文件会报 ImportError

经验法则:自己写的小脚本之间互导,放在同一目录直接 import 即可;正式项目规划成包,统一从项目根目录启动,导入路径写全包名。

6Python 标准库速览

"内置电池"意味着大量实用模块无需安装,开箱即用:

模块用途
os / sys操作系统接口、系统与解释器信息
pathlib面向对象的路径处理(第11章)
math / random / statistics数学、随机数、统计
datetime / time日期时间
re正则表达式(第13章)
json / csvJSON、CSV 数据处理(第11章)
collectionsCounter/defaultdict/deque 等增强容器(第14章)
itertools / functools迭代工具、函数式工具(第14章)
logging日志记录(第10、14章)
argparse命令行参数解析(第14章)
threading / multiprocessing / asyncio多线程 / 多进程 / 异步
unittest / doctest单元测试

7第三方库与依赖管理

标准库之外的海量库发布在 PyPI(pypi.org),通过 pip 安装(镜像源配置见第 1 章):

pip install requests jieba scikit-learn     # 安装
pip install "django>=4.2,<5.0"              # 指定版本范围
pip list --outdated                          # 查看可升级的包
pip install -U pandas                        # 升级
pip freeze > requirements.txt                # 导出当前环境所有依赖及版本
# requirements.txt 示例
requests==2.32.3
jieba==0.42.1
scikit-learn>=1.4
pandas~=2.2.0          # ~= 表示兼容版本
# 换台电脑/部署到服务器:一条命令还原整套依赖
python -m venv .venv
.venv\Scripts\activate
pip install -r requirements.txt

本教程 NLP 篇将用到的库

库安装用途
jiebapip install jieba中文分词、关键词提取
scikit-learnpip install scikit-learn文本特征、分类算法、评估
pandas / numpypip install pandas numpy数据处理与数值计算
matplotlib / wordcloudpip install matplotlib wordcloud绘图与词云
gensimpip install gensimword2vec 词向量
nltk / spacypip install nltk spacy英文 NLP / 实体识别

8标准项目目录组织

sentiment_project/           # 一个 NLP 项目的推荐结构
├── .venv/                   # 虚拟环境(不提交到 git)
├── data/
│   ├── raw/                 # 原始语料
│   └── processed/           # 清洗后的数据
├── models/                  # 训练好的模型
├── src/                     # 源代码包
│   ├── __init__.py
│   ├── preprocess.py        # 文本预处理
│   ├── train.py             # 训练
│   └── predict.py           # 预测
├── tests/                   # 测试
│   └── test_preprocess.py
├── main.py                  # 程序入口
├── requirements.txt         # 依赖清单
├── .gitignore               # git 忽略规则
└── README.md                # 项目说明

配套工具推荐:pipreqs(只扫描代码生成精简 requirements)、pip-tools(锁定依赖版本)、大型项目可了解 poetry / uv 等新一代环境与依赖管理工具。

9本章小结与练习

📌 关键结论

  • .py 即模块,文件夹(含 __init__.py)即包;导入 = 执行并缓存模块。
  • 默认写 import mod,高频名字才 from import;别名遵守社区约定。
  • 启动逻辑放进 if __name__ == "__main__":;不要和标准库重名。
  • 每个项目一个 venv,用 requirements.txt 固化与复现依赖。
练习1:from math import sqrt 和 import math 后使用 sqrt 分别怎么写?
前者已把 sqrt 名字导入当前空间,直接 sqrt(16);后者必须写 math.sqrt(16),通过模块名限定来源。
练习2:为什么把测试代码放在 if __name__ == "__main__": 里?
直接运行该文件时 __name__ 为 "__main__",测试代码执行;被其他模块 import 时 __name__ 为模块名,测试代码不执行,从而避免导入副作用,让模块既可独立运行自测,又能被安全复用。
练习3:新建 mypackage 包,内含 string_tools.py(实现 reverse_str、count_vowels),在 main.py 中调用。
建 mypackage/ 文件夹与空 __init__.py;string_tools.py 中实现两函数;main.py 写 from mypackage.string_tools import reverse_str, count_vowels,从项目根目录运行 python main.py 即可。注意不要把文件命名为 string.py 以免遮蔽标准库。