模块与包 — import 的三种写法与包的组织
模块与包 — import 的三种写法与包的组织
这一章讲代码怎么从一个文件长成一个项目:模块是文件,包是文件夹,import 是把它们接起来的插头。 原书对新手最友好的一点是:它明说三种包形态对小项目「差别微小」,不制造焦虑; 真正的重点是依赖管理——pip、虚拟环境、requirements.txt 三件套。
1. 这一章讲什么
模块(module):包含 Python 代码的文件或文件夹,通常围绕一个功能(如 math 模块装数学函数)。 import 就是把模块接进当前代码,是 Python 里最常用的操作之一。1
包(package):自 Python 3.3 起分两类——新的命名空间包(命名空间 = 各名字各领一块地、互不影响)和旧的常规包。「新不等于更好,只是另一种做法; 只要包小,差别可以忽略。」2
本章的两条腿:
① 自己写包:三种形态怎么选、__init__.py 是干嘛的
② 装别人的包:pip、虚拟环境、requirements.txt、pip freeze
图说:①管「代码怎么分文件」,②管「依赖怎么不打架」。2. 核心原理一:常规包——__init__.py 是包的「门面」
解决什么问题: 项目大了,一个文件装不下;相关的几个模块该住同一个文件夹,对外一个名字。
怎么做(主线例子): 目录结构:
my_package/
__init__.py ← 必须有(可以为空):它的「存在」就把这个文件夹标记成包
math_utils.py # def add(a, b): ...
string_utils.py # def get_first_character(s): ...
main.py # 在外面用__init__.py 在包被 import 时执行。它的经典用法是定义公开接口——把内部模块的功能挑出来挂到包名下:3
# my_package/__init__.py
from .math_utils import add
from .string_utils import get_first_character.math_utils 里的点号表示「本包内的」模块。之后使用方有两种舒服写法:
import my_package
my_package.add(1, 2) # 3 —— 出处永远可见(第 09 章的规矩)
from my_package import add, get_first_character # 包帮你转交,直接用名字
add(1, 2) # 3如果 __init__.py 是空的,功能照样可用,但使用方要自己走到内部模块去拿,啰嗦一点:3
from my_package.math_utils import add # 路径长一层为什么有效、边界在哪: __init__.py 让包能策展自己的公开面孔(用户只见 add,不见内部怎么分文件)——
封装思想在文件层级的复用。边界:它理论上能装任何代码,但「最常用于定义公开接口」;
塞太多初始化逻辑进去,import 这个包就会变慢、副作用难查——保持它瘦。
3. 核心原理二:命名空间包——没有 __init__.py,同名包可以拼起来
解决什么问题: 一个包想拆到多个文件夹,甚至多个项目上;或者想有多个同名包、内容不同,换路径即切换版本。
怎么做(主线例子): 目录里有两个 utils 子包,分别住在 math/ 和 string/ 下,都没有 __init__.py:
my_project/
math/utils/math_utils.py # def add(a, b): ...
string/utils/string_utils.py # def get_first_character(s): ...
main.py把两条路径都加进 sys.path(Python 找模块的目录清单),然后:
import sys
sys.path.extend(['/my_project/string', '/my_project/math'])
from utils import math_utils, string_utils
math_utils.add(2, 3) # 5
string_utils.get_first_character("hello") # h两个不同位置的 utils 拼成了同一个命名空间包。4
为什么有效、边界在哪: 用处书里点了两条——同名包可以有不同内容(换 sys.path 就换实现,版本切换的朴素版), 以及大包可以物理拆开。边界:需要手工管 sys.path,容易乱;小项目用不上,作者明说「不写大库的话,这段可以跳过,以后再看」。5
判断(我们的,不是书里的): 「空
__init__.py起步」是 2026 年依然正确的默认; 但「__init__.py里 re-export 公开接口」的做法在现代 Python 里逐渐被「让使用方显式写全路径」取代 (理由还是第 01 章那句:出处可见)。团队大、接口稳定时用 re-export 策展门面;脚本和小工具,空文件最省心。 如果错,会错在: 若项目用 IDE/类型检查器深度依赖「包级名字」做补全与静态检查,re-export 会显著改善体验, 那时空文件的「省事」是假的省。
4. 核心原理三:pip 与虚拟环境——依赖管理三件套
解决什么问题: 从网上拿包(NumPy 之类);不同项目要不同版本的同一个包,全局装会互相覆盖。
怎么做: 三件套,一条流水线:6
pip install numpy # ① 装包(在激活的虚拟环境里!)
python -m venv venv # ② 建虚拟环境(自包含目录:一套自己的 Python + 库)
source venv/bin/activate # 激活(Linux;Windows 是 venv\Scripts\activate),提示符出现 (venv)
deactivate # 退出
pip install -r requirements.txt # ③ 按清单批量装(换机器、重建环境,一条命令)
pip freeze > requirements.txt # 反方向:环境里没有清单时,从已装的包生成清单缺包时报错长这样,且很好认:ModuleNotFoundError: No module named 'numpy'——
书里的建议很「2026」:拿报错去问你的 AI 工具,它「几乎肯定能告诉你该装哪个包」。7
虚拟环境的两个使用心法(书里给的):
- 不常用的项目:删掉整个 venv 文件夹即彻底卸载(所有数据都在这一个目录里), 要用时按 requirements.txt 重建;
- 协作项目:没有清单就
pip freeze生成一个——「这是值得养成的好习惯」。
为什么有效: 每个项目一个独立世界,版本冲突被目录边界物理隔离; requirements.txt 把「环境」从不可见的机器状态变成可以进版本库(Git 管理的那个代码仓库)的文本文件—— 环境和代码一起被 Git 管理(第 01 章的「大项目用纯代码文件,才能上 Git」在这里闭环)。
5. 作者的判断与证据
书里给了证据的: 三种包的目录结构和 import 写法都可照做;pip 命令序列是可直接执行的操作步骤。36
作者的判断(务实到近乎泄气,这是优点):
- 「小项目三种包差别微小,别纠结」——准确的降噪;
- 「空
__init__.py可能是最好的选择,但取决于你想干嘛」——把决定权还给场景; - 「最终重要的是代码易读易维护」——本章多次回到这条总纲。
6. 边界与局限
- sys.path 的机制只露了一面(命名空间包例子),site-packages、.pth、PYTHONPATH 这些环境机制没展开;
- 打包发布(把自己的包发布到 PyPI:pyproject.toml、构建后端)完全没讲——本书管「用包」不管「发包」;
- 依赖版本冲突的解决(约束版本、锁文件如 uv/poetry)没提——pip freeze 是 2010 年代方案,2026 年的 uv 更快更稳(补充,不在书里);
- 循环依赖在 import 层的同款问题(模块 A import B、B 又 import A)在第 10 章提过原则,本章没结合包结构再讲。
7. 可带走的
本章主线例子,一行写完: my_package/__init__.py 里写 from .math_utils import add →
使用方 import my_package 后 my_package.add(1, 2) 即用;空 __init__.py 则使用方自己写全路径。3
- 模块 = .py 文件;包 = 含
__init__.py的文件夹(命名空间包除外,它没有)。 - 三种包形态对小项目差别微小;默认空
__init__.py,大库再考虑门面 re-export 或命名空间包。 __init__.py的经典角色是策展公开接口;保持它瘦。- 装包先建虚拟环境:
python -m venv venv→ 激活 →pip install;提示符的(venv)是你的位置感。 - 依赖清单进版本库:协作项目
pip freeze > requirements.txt;重建环境pip install -r requirements.txt。 - ModuleNotFoundError 几乎总是「没装/没激活环境」;删掉 venv 文件夹 = 彻底卸载该项目环境。
- 命名空间包记住一句话就够:同名包可拼接、可换路径——用到再回来查。
8. 原文地图
| 主题 | 原书章 | 原文位置 |
|---|---|---|
| 模块定义、import | 8 Python Modules and Packages | text/24-ch08-8-python-modules-and-packages.txt:5(搜「folder or file」) · text/24-ch08-8-python-modules-and-packages.txt:8(搜「most common operations」) |
| 两种包、三种形态、可跳过建议 | 8 Python Modules and Packages | text/24-ch08-8-python-modules-and-packages.txt:9(搜「namespace packages」) · text/24-ch08-8-python-modules-and-packages.txt:22(搜「skip this section」) |
| 常规包与 init.py 的角色 | 8 Python Modules and Packages | text/24-ch08-8-python-modules-and-packages.txt:24(搜「Regular Packages」) · text/24-ch08-8-python-modules-and-packages.txt:29(搜「public interface」) |
| 目录结构与两种 import 写法 | 8 Python Modules and Packages | text/24-ch08-8-python-modules-and-packages.txt:37(搜「my_package/」) · text/24-ch08-8-python-modules-and-packages.txt:59(搜「import my_package」) · text/24-ch08-8-python-modules-and-packages.txt:65(搜「from my_package import」) |
| 空 init.py 的啰嗦代价 | 8 Python Modules and Packages | text/24-ch08-8-python-modules-and-packages.txt:71(搜「slightly more verbose」) |
| 命名空间包与 sys.path | 8 Python Modules and Packages | text/24-ch08-8-python-modules-and-packages.txt:78(搜「Namespace Packages」) · text/24-ch08-8-python-modules-and-packages.txt:102(搜「sys.path」) |
| 同名包拼接的用途 | 8 Python Modules and Packages | text/24-ch08-8-python-modules-and-packages.txt:113(搜「what’s the point」) |
| pip 与 ModuleNotFoundError | 8 Python Modules and Packages | text/24-ch08-8-python-modules-and-packages.txt:120(搜「Installing Packages with Pip」) · text/24-ch08-8-python-modules-and-packages.txt:130(搜「No module named」) |
| 虚拟环境与激活 | 8 Python Modules and Packages | text/24-ch08-8-python-modules-and-packages.txt:138(搜「self-contained directory」) · text/24-ch08-8-python-modules-and-packages.txt:148(搜「(venv)」) |
| requirements.txt 与 pip freeze | 8 Python Modules and Packages | text/24-ch08-8-python-modules-and-packages.txt:164(搜「pip install -r」) · text/24-ch08-8-python-modules-and-packages.txt:168(搜「pip freeze」) |
Footnotes
-
出处:「8 Python Modules and Packages」第 3–8 段(
text/24-ch08-8-python-modules-and-packages.txt:5,搜「folder or file」)。 ↩ -
出处:「8 Python Modules and Packages」第 9–23 段(
text/24-ch08-8-python-modules-and-packages.txt:9,搜「namespace packages」;「新不等于更好」见同段)。 ↩ -
出处:「8 Python Modules and Packages」第 24–77 段(
text/24-ch08-8-python-modules-and-packages.txt:26,搜「has to contain a file called」;「public interface」见第 29 段;代码示例见第 36–77 段)。 ↩ ↩2 ↩3 ↩4 -
出处:「8 Python Modules and Packages」第 78–118 段(
text/24-ch08-8-python-modules-and-packages.txt:80,搜「same name」;代码见第 99–111 段)。 ↩ -
出处:「8 Python Modules and Packages」第 113–118 段(
text/24-ch08-8-python-modules-and-packages.txt:113,搜「multiple packages」)与第 22 段(「可以先跳过」,搜「skip this section」)。 ↩ -
出处:「8 Python Modules and Packages」第 120–175 段(
text/24-ch08-8-python-modules-and-packages.txt:138,搜「self-contained directory」;第 148 段,搜「(venv)」;第 162 段,搜「pip install -r」;第 166 段,搜「pip freeze」)。 ↩ ↩2 -
出处:「8 Python Modules and Packages」第 125–133 段(
text/24-ch08-8-python-modules-and-packages.txt:130,搜「No module named」;「问 AI 工具」见第 133 段,搜「AI tool」)。 ↩