🧮 第5章 环境搭建
📄 一句话概括

装包只有一条命令 pip install,麻烦的是「装哪个版本、装到哪去、以后怎么复原」。 Demo 列出当前环境 91 个已装包, 再用一份五行清单去核对—— numpy 2.5.2 满足、pyyaml 6.0.3 满足、matplotlib 3.11.1 满足,pandas 和 torch 根本没装, 五个只过三个。 最后一段讲清楚为什么推荐写 python -m pip 而不是 pip。

📝 本节和前面几节的关系

上一节建好了空的虚拟环境,这一节往里装东西。 这是全书最后一个小节,也是实操里最高频的动作——2.x 节的 numpy、4.2 节的 TF-IDF、 图像任务的 Pillow,全靠这一步。装包装错地方,是新手最常见的时间黑洞。

一、三条命令,从简到繁

最简单:pip install 包名。装最新版。 指定版本:pip install 包名==2.5.2。复现别人的环境时用。 按清单装:python -m pip install -r requirements.txt。真实项目的标准做法。 装完想确认:pip list 看全部、pip show 包名 看单个的详情(版本、依赖、装在哪)、 pip freeze 输出一份可以直接当 requirements.txt 用的清单。 pip freeze 是「我这环境到底有什么」的权威答案。

二、requirements.txt 的四种写法

numpy:任意版本。图省事时用,但不可复现——今天装的和明天装的可能不是同一个版本。 numpy==2.5.2:必须正好是它。复现实验的标准写法,缺点是锁得太死,别的包要求不同版本时会冲突。 numpy>=2.0:不低于 2.0。 只要求「有某个新特性」时用,但它允许装到未来任何版本,哪天出了破坏性变更就会突然崩。 numpy~=2.5.2:2.5.x 里最新的,但不跨到 2.6。 这在「要补丁、不要破坏性变更」时最合适——兼顾了安全和更新。 实际操作里常见的组合是: 顶层依赖用 ~= 或 >= 写宽一点,然后用 pip freeze 生成一份锁死的清单给部署用。 一份给人看,一份给机器看。

三、装到哪去了:最容易错的一步

pip install 装到「当前这个 pip 所属的环境」。而 pip 是哪个环境的,又取决于你敲的是哪一个。 这就有了套娃。 三种写法的差别:pip install 包名 装到 PATH 上第一个 pip 的环境(可能不是你想的); python -m pip install 包名 装到这个 python 的环境;激活环境后 pip install 装到被激活的环境。 推荐永远用 python -m pip。 它把「哪个 python」和「装包」绑死在一起,少一层歧义。 排查「装了却 import 不到」时,第一件事就是换成这个写法再试一次。 还有一个隐蔽的坑: 装完不重启解释器,某些情况下 import 不到。 Jupyter 里尤其明显——它启动时就把模块表加载好了, 装完包得重启内核。这不是装错了,是没刷新。

四、装不上怎么办:四种常见原因

一是网络。 国内直连 PyPI 慢,换镜像源: pip install 包名 -i https://pypi.tuna.tsinghua.edu.cn/simple。 建议一次配好,以后不用再敲(pip config set global.index-url ...)。 二是没有现成的轮子(wheel)。 纯 Python 包好办,带 C 扩展的包需要编译, Windows 上没装编译器就会失败。这时候看报错里有没有「building wheel」——有就说明在尝试编译。 三是版本不兼容。 报错里会写「requires Python >=3.10」或者「no matching distribution」。 这时候要么换包版本,要么换 Python 版本。 四是权限。 系统 Python 的 site-packages 可能要管理员权限。 这也是用虚拟环境的理由之一——自己的文件夹,不用任何权限。

五、最后:把这一章连起来

三节合起来是一条线: 装 Python(别装 32 位、记住 PATH)→ 建虚拟环境(隔离、可删可重建)→ 装包(用 python -m pip、 写 requirements.txt)。 这条线跟算法无关,但它决定了你能不能把书上的代码跑起来。 很多人卡在这里就放弃了,误以为自己「学不会机器学习」——其实只是 pip 装到了另一个 Python 里。 全书到这里结束。 回头看: 十七个算法的原理在第 2、3 章,判断好坏的方法在第 4 章,把它们跑起来的地基在第 5 章。 三样都齐了,剩下的就是拿真实数据反复练。

六、动手 Demo:数一数环境里有什么,再核对一份清单

这一段不需要联网。 它做两件事:列出当前环境已装的包(这里 91 个),再拿一份五行清单去逐条核对——谁满足、 谁版本不对、谁压根没装。 核对的逻辑就是 pip install -r 干的事,只是我们把它拆开、把每一步的结果打出来,好看清它在干什么。

from importlib import metadata

# 这个环境里已经装了什么
for d in metadata.distributions():
    print(d.metadata["Name"], d.version)

# requirements.txt 里的四种写法,含义完全不同
#   numpy            任意版本
#   numpy==2.5.2     必须正好是它
#   numpy>=2.0       不低于 2.0
#   numpy~=2.5.2     2.5.x 里最新的

# 永远用这种写法装包:把「哪个 python」和「装包」绑死
#   python -m pip install -r requirements.txt

实跑输出:

--------------------------------------------------------------------
装包:把「要哪几行」写清楚
--------------------------------------------------------------------

  装包本身只有一条命令:`pip install 包名`。麻烦的从来不是敲命令,而是
  **「装哪个版本」「装到哪去」「以后怎么复原」**这三件事。

  【一】这个环境里现在有什么
  包                            版本
  ------------------------------------------------
  anyio                         4.15.1
  av                            18.1.0
  bitarray                      3.11.0
  bitstring                     4.4.0
  certifi                       2026.7.22
  cffi                          2.1.1
  charset-normalizer            3.5.1
  click                         8.5.0
  一共 **91** 个已安装的包。**装包之前先数一遍,装完再数一遍**
  ——数字变了,才说明真的装进去了。

  【二】requirements.txt 怎么读
  实际项目不会一句一句 `pip install`,而是把依赖写进一个文本文件,**一行一个约束**,
  然后 `pip install -r requirements.txt` 一次装完。写法有几种,含义完全不同:
  写法              含义                          什么时候用
  ----------------------------------------------------------------------------
  numpy             任意版本                      不关心,能跑就行
  numpy==2.5.2      必须正好是这个版本            复现别人的环境
  numpy>=2.0        不低于 2.0                    只要求有某个新特性
  numpy~=2.5.2      2.5.x 里最新的(不改中间位)  要补丁不要破坏性变更

  【三】拿这份清单去核对现有环境
  包            已装版本      要求            结果
  ----------------------------------------------------
  numpy         2.5.2         == 2.5.2        满足
  pyyaml        6.0.3         >= 6.0          满足
  matplotlib    3.11.1        >= 3.8          满足
  pandas        未安装        >= 2.0          缺
  torch         未安装        >= 2.0          缺
  **五个要求里 3 个满足,剩下的要么没装、要么版本不对。**
  `pip install -r requirements.txt` 干的就是这件事:逐条核对,不满足就去装。

  【四】装到哪去了:这是最容易错的一步
  `pip install` 装到「当前这个 pip 所属的环境」。而 `pip` 是哪个环境的,
  又取决于你敲的是哪一个——**这就有了一层套娃。**
  你敲的命令                        装到哪
  ----------------------------------------------------------------------------------------
  pip install 包名                  PATH 上第一个 pip 所属的环境(可能不是你想的那个)
  python -m pip install 包名        这个 python 所属的环境
  (激活环境后)pip install 包名    被激活的那个环境
  **推荐永远用 `python -m pip` 这种写法。**
   它把「哪个 python」和「装包」绑死在一起,少一层歧义。
  **这是排查「装了却 import 不到」时第一个要换的写法。**

「一共 91 个包」这个数本身没什么意义,但它的变化有意义。 装包前后各数一次, 数字变了才说明真装进去了——这比「没报错」可靠得多。 核对表那五行是这一节的落点。numpy 2.5.2 对上 ==2.5.2,满足;pyyaml 6.0.3 满足 >=6.0; matplotlib 3.11.1 满足 >=3.8;pandas 和 torch 是「未安装」,直接判缺。 「五个里三个满足」这个结果很典型。真实项目里清单几十行,永远是「大部分满足、少数几个不对劲」。 关键是让工具去核,不要靠人记。 最后那段讲「装到哪」的三种写法,值得抄在便签上。 pip install 和 python -m pip install 看起来只差几个字符,装错地方的时候能查一整天。

30 秒实验

  1. 用 pip freeze 导出一份清单,再和新环境对比,差多少行?
  2. 把清单里的 >= 全改成 == 并写上当前版本,这份清单在别的机器上能一次装成吗?
  3. 故意写一个不存在的包名去核对,看报错长什么样——和「版本不满足」的报错有什么区别?

常见误区

  1. 用 pip 而不是 python -m pip。前者跟着 PATH 走,可能装到别的环境里。 后者把「哪个 python」和「装包」绑死。
  2. requirements.txt 全写 >=。今天能装,半年后某个包出了破坏性更新就崩。 给人看的写宽,给机器装的锁死。
  3. 装完不验证。「没报错」不等于「装上了」。 装完立刻 python -c "import 包名; print(包名.__version__)"。
自测三问(点开对答案) 1. `pip install` 和 `python -m pip install` 有什么区别? —— 前者用 PATH 上第一个 pip,装到它所属的环境; 后者用「这个 python」的 pip,装到这个 python 的环境。后者没有歧义,推荐使用。 2. `==`、`>=`、`~=` 三种写法分别适合什么场景? —— `==` 锁死版本,用于复现;`>=` 只要不低于某个版本,用于「需要某个新特性」; `~=` 允许补丁级更新但不跨次版本,用于「要修 bug 但怕破坏性变更」。 3. 装完包 import 不到,最先检查什么? —— 先确认装到了哪个环境:`python -c "import sys; print(sys.executable)"`, 再确认用的 pip 是不是同一个环境。Jupyter 里还要重启内核。

全书完。 十七个算法、一套评估方法、一条环境搭建路线——接下来该做的是找一份自己真正关心的数据, 把这条流水线从头跑一遍。

相关笔记