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

虚拟环境就是「一个装了 Python 的独立文件夹」,让每个项目有自己的包仓库。 Demo 真的建了一个:命令 python -m venv 目录名,一秒建好,里面四样东西(Include、Lib、 Scripts、pyvenv.cfg)。 新建环境里 site-packages 有 0 个条目,而母体环境装了 91 个包——这就是「隔离」的实际样子。

📝 本节和前面几节的关系

上一节把 Python 装到了系统里。这一节要解决的是「装完之后怎么用才不打架」。 答案是: 系统里那个 Python 只当母体,真正干活的是每个项目自己的虚拟环境。 这一节跟算法无关, 但它是后面所有实操的地基——3.2 节的 LSA、4.2 节的 TF-IDF,如果你装不上 jieba,就卡在这儿。

一、它到底是什么:一个文件夹

虚拟环境不是什么高深的东西,它就是一个文件夹, 里面有一份 Python 的拷贝(Windows 上是真的复制 exe,Linux 上是符号链接)、一份标准库、 一个空的包目录,外加一个配置文件。 Demo 里真的建了一个:python -m venv demo_venv,不到一秒就建好了。 目录里有四样:Include(编译 C 扩展要用的头文件)、Lib(标准库副本, 以及 site-packages 也就是装包的地方)、Scripts(python.exe、pip.exe 这些可执行文件)、 pyvenv.cfg(这个环境的名片)。 --without-pip 是我加上去的,因为它只建目录、不下载任何东西,断网也能跑。 正常建环境时不加这个参数,pip 会一起准备好。

二、pyvenv.cfg:它怎么知道自己是虚拟环境

这个文件只有几行,但它是整个机制的钥匙。 home 指向母体 Python 的目录,include-system-site-packages 说「要不要顺带看到外面装的包」, version 是版本号,command 是建它时敲的那条命令(方便回溯)。 include-system-site-packages 默认是 false, 也就是完全隔离——这个环境看不到母体装的任何第三方包。 想要能看见就改成 true,但那样隔离就破了,一般不这么干。 Python 启动时会往上找这个文件。 找到就说明「我在虚拟环境里」,于是把 sys.prefix 设成环境目录, 把 sys.base_prefix 保留成母体目录。两个值不一样,就是虚拟环境的判据。

三、隔离到什么程度:一个数字说清楚

Demo 里新建的环境,site-packages 有 0 个条目。而母体环境装了 91 个第三方包。 这就是「干净抽屉」的实际样子。 这件事有两面。好处是依赖彻底分开,A 项目装崩了不影响 B 项目,删掉文件夹就完全复原。 代价是每个环境都要重新装一遍依赖,而且硬盘上会有好几份 numpy。 代价用 requirements.txt 就能抹平: 把依赖写进一个文本文件,新环境一句 pip install -r requirements.txt 就复原。 下一节细讲。

四、激活:本质是改 PATH

建好之后要「激活」(activate)。 Windows 上敲 .venv\Scripts\activate,macOS/Linux 上敲 source .venv/bin/activate。 退出是 deactivate。 激活做的事非常朴素:把 .venv 的 Scripts 目录插到 PATH 最前面。 于是你敲 python 和 pip, 先找到的就是环境里的那一份。命令行提示符前面通常会多出 (.venv),那是提醒你现在在哪个环境。 所以「激活」不是必须的。 你也可以直接写全路径调用:.venv\Scripts\python.exe 脚本.py。 这在写脚本、配定时任务时反而更可靠——因为定时任务里没有「激活」这一步,写全路径就不会跑错环境。 最后一件事:不要把 .venv 提交到 git。 它包含绝对路径和二进制文件,换台机器就废了。 该提交的是 requirements.txt——别人 clone 下来,建环境、装依赖,两句话就复原。

五、动手 Demo:建一个出来,看它到底隔离了什么

真的建一个虚拟环境,不是画示意图。 用 python -m venv 加上 --without-pip,一秒建好,断网也能跑。 建完读它的配置文件、列它的目录、再用它自己的 python 打印 sys.prefix,看它和母体有什么区别。 最后会把临时目录删掉——这段代码是安全的,不会在你机器上留垃圾。

import subprocess
import sys

# 建一个虚拟环境:python -m venv 目录名
subprocess.run([sys.executable, "-m", "venv", "--without-pip", ".venv"])

# 建完之后目录里有四样东西
#   Include\   编译 C 扩展用的头文件
#   Lib\       标准库副本 + site-packages(装包的地方)
#   Scripts\   python.exe、pip.exe
#   pyvenv.cfg 指向哪个母体 Python

# 判断「现在在不在虚拟环境里」只要一行
print(sys.prefix != sys.base_prefix)

实跑输出:

--------------------------------------------------------------------
虚拟环境:给每个项目一个干净的抽屉
--------------------------------------------------------------------

  为什么需要虚拟环境?因为**不同项目要的包版本会打架**。
  A 项目要 numpy 1.x,B 项目要 numpy 2.x,装在同一个环境里只能留一个,
  **另一个项目当场跑不起来。**
   虚拟环境的办法是:每个项目一个独立目录,里面有自己的 Python 副本和自己的包仓库。

  【一】造一个出来看看
  命令是 `python -m venv 目录名`。这里加了 `--without-pip`,因为它只建目录、不下载任何东西,
  **断网也能跑**。返回码 0。
  目录里的东西    作用
  ----------------------------------------------------------------------
  Include         编译 C 扩展时要用的头文件
  Lib             标准库副本,以及 site-packages(装包的地方)
  Scripts         python.exe、pip.exe 这些可执行文件都在这里
  pyvenv.cfg      这个环境的名片:指向哪个基础 Python

  【二】pyvenv.cfg 里写着什么
  键                            值
  ------------------------------------------------------------------------------
  home                          C:\Users\admin\.workbuddy\binaries\python\v...
  include-system-site-packages  false
  version                       3.13.14
  executable                    C:\Users\admin\.workbuddy\binaries\python\e...
  command                       C:\Users\admin\.workbuddy\binaries\python\e...
  **最关键的是 `home` 和 `include-system-site-packages`。**
   前者说「我这个环境是照着谁建的」,后者说「要不要顺带看到外面装的包」。
  **默认是 false,也就是完全隔离。**

  【三】它确实是一个「另外的」Python
                sys.prefix(自己)                sys.base_prefix(母体)
  --------------------------------------------------------------------------------
  当前环境      <Python>\envs\default             <Python>\versions\3.13.12
  新建的环境    <Temp>\_venv44_wl27gt4m\demo_venv <Python>\versions\3.13.12
  **两个值不一样,就说明它在虚拟环境里。**
   代码里判断这件事只要一行:`sys.prefix != sys.base_prefix`。
  **很多库靠这一行决定要不要走特殊逻辑。**

  【四】干净到什么程度
  新建的环境里,site-packages 有 **0** 个条目——**一个第三方包都没有**。
  这就是「干净抽屉」的意思:里面只有标准库,装什么全靠你自己。
  顺便说,这也解释了为什么「给虚拟环境装包」必须用它自己的 pip:
  包是装到 `site-packages` 里的,而每个环境有自己的一份。
  **用外部的 pip 装,装的是外部环境,新环境里还是空的。**

  【五】每天要用的三句
  场景                      命令
  --------------------------------------------------------------------------
  建环境                    python -m venv .venv
  激活(Windows)           .venv\Scripts\activate
  激活(macOS/Linux)      source .venv/bin/activate
  退出                      deactivate
  激活的本质是**改 PATH**:
  把 .venv 的 Scripts 目录插到最前面,于是 `python` 和 `pip` 都先找到环境里的那一份。
  **所以「激活」不是必须的——你也可以直接写全路径调用,只是麻烦。**

目录表那四行建议记一下。Lib 是装包的地方,Scripts 是可执行文件, Include 是编译 C 扩展用的头文件,pyvenv.cfg 是配置。 「装了包在哪」这个问题,答案永远在 Lib\site-packages 里。 pyvenv.cfg 那张表里,两个值最关键:home 和 false。 home 说「照着谁建的」, false 说「完全隔离」。看到 false 就知道:这个环境里必须自己装包,别指望母体的。 第三张表是硬证据:两个环境的 sys.prefix 不一样。 当前环境是 <Python>\envs\default, 新建的是 <Temp>\_venv44_xxx\demo_venv, 而两者的 sys.base_prefix 都是同一个 <Python>\versions\3.13.12。同一个母体,两个身份。 site-packages 里 0 个条目,是这个 Demo 最该记住的数字。 母体环境有 91 个包,新环境一个都没有。 这就是为什么「给虚拟环境装包」必须用它自己的 pip。

30 秒实验

  1. 把 --without-pip 去掉再建一次,Scripts 目录里会多出什么文件?
  2. 把 pyvenv.cfg 里的 include-system-site-packages 改成 true, 新环境的 python 能看到母体装的包吗?
  3. 建两个环境,一个装 numpy 1.x、一个装 2.x,能同时存在吗?为什么?

常见误区

  1. 把 .venv 提交到 git。里面是绝对路径和二进制文件,换台机器就废。 该提交的是 requirements.txt。
  2. 激活了环境,却用系统的 pip 装包。装的还是系统环境。 装完用 python -c "import 包名" 验一下,或者看 sys.executable。
  3. 在虚拟环境里用 sudo pip install。sudo 会切到 root 的 PATH,装到系统里去, 还可能把权限搞乱。虚拟环境里永远不要 sudo。
自测三问(点开对答案) 1. 虚拟环境本质上是什么? —— 一个文件夹,里面有一份 Python 拷贝、一份标准库、一个空的包目录和一个 pyvenv.cfg。 没有魔法,删掉文件夹就完全复原。 2. 怎么用一行代码判断当前在不在虚拟环境里? —— `sys.prefix != sys.base_prefix`。前者是当前环境的根,后者是母体的根,在虚拟环境里两者不同。 3. 「激活」这个动作实际做了什么? —— 把虚拟环境的 Scripts(或 bin)目录插到 PATH 最前面, 让 `python` 和 `pip` 先找到环境里的那一份。不改任何代码,只改环境变量。

环境建好了,里面还是空的。5.3 节讲怎么把需要的包装进去,以及怎么让「装了什么」这件事可复现。

相关笔记