跳转至

模块与包 — 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

  1. 模块 = .py 文件;包 = 含 __init__.py 的文件夹(命名空间包除外,它没有)。
  2. 三种包形态对小项目差别微小;默认空 __init__.py,大库再考虑门面 re-export 或命名空间包。
  3. __init__.py 的经典角色是策展公开接口;保持它瘦。
  4. 装包先建虚拟环境:python -m venv venv → 激活 → pip install;提示符的 (venv) 是你的位置感。
  5. 依赖清单进版本库:协作项目 pip freeze > requirements.txt;重建环境 pip install -r requirements.txt。
  6. ModuleNotFoundError 几乎总是「没装/没激活环境」;删掉 venv 文件夹 = 彻底卸载该项目环境。
  7. 命名空间包记住一句话就够:同名包可拼接、可换路径——用到再回来查。

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」)

  1. 出处:「8 Python Modules and Packages」第 3–8 段(text/24-ch08-8-python-modules-and-packages.txt:5,搜「folder or file」)。 ↩

  2. 出处:「8 Python Modules and Packages」第 9–23 段(text/24-ch08-8-python-modules-and-packages.txt:9,搜「namespace packages」;「新不等于更好」见同段)。 ↩

  3. 出处:「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 段)。 ↩↩↩↩

  4. 出处:「8 Python Modules and Packages」第 78–118 段(text/24-ch08-8-python-modules-and-packages.txt:80,搜「same name」;代码见第 99–111 段)。 ↩

  5. 出处:「8 Python Modules and Packages」第 113–118 段(text/24-ch08-8-python-modules-and-packages.txt:113,搜「multiple packages」)与第 22 段(「可以先跳过」,搜「skip this section」)。 ↩

  6. 出处:「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」)。 ↩↩

  7. 出处:「8 Python Modules and Packages」第 125–133 段(text/24-ch08-8-python-modules-and-packages.txt:130,搜「No module named」;「问 AI 工具」见第 133 段,搜「AI tool」)。 ↩