虚拟环境就是「一个装了 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 秒实验
- 把
--without-pip去掉再建一次,Scripts 目录里会多出什么文件? - 把 pyvenv.cfg 里的
include-system-site-packages改成 true, 新环境的 python 能看到母体装的包吗? - 建两个环境,一个装 numpy 1.x、一个装 2.x,能同时存在吗?为什么?
常见误区
- 把 .venv 提交到 git。里面是绝对路径和二进制文件,换台机器就废。 该提交的是 requirements.txt。
- 激活了环境,却用系统的 pip 装包。装的还是系统环境。
装完用
python -c "import 包名"验一下,或者看 sys.executable。 - 在虚拟环境里用
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 节讲怎么把需要的包装进去,以及怎么让「装了什么」这件事可复现。
- ☐ (本节完,下一节 5.3 第三方包的安装)