Python 的生态繁荣离不开其丰富的第三方库,但“依赖地狱(Dependency Hell)”也一直是开发者头顶的乌云。
很多新手(甚至老手)经常面临这样的困惑:
- “为什么我的代码在本地能跑,发给同事就报错?”
- “Conda 和 pip 到底能不能混用?”
- “
requirements.txt里的包为什么越来越多,即使我删了代码?”
本文将为你梳理 Python 包管理的两条核心路径:原生 PyPI 路径 与 科学计算 Conda 路径,并深入解析 venv 原理、现代工具 uv 的崛起,以及项目打包的工程规范。
1. Python 包管理的两条“平行宇宙”
在开始之前,我们需要建立一个全局认知:Python 包管理主要分为两个流派。
- PyPI 流派(Native):
- 核心:
pip+venv(或poetry,uv,pdm)。 - 源头:Python Package Index (PyPI)。
- 特点:轻量级,适合 Web 开发、脚本工具、纯 Python 项目。
- 核心:
- Conda 流派(Scientific):
- 核心:
conda/mamba。 - 源头:Anaconda.org (Defaults, Conda-forge)。
- 特点:重量级,解决了非 Python 依赖(如 C/C++ 编译库、CUDA、GDAL 等)的二进制兼容性问题,适合数据科学、机器学习。
- 核心:
2. 深入原生:venv 与环境复现
2.1 venv:最基础的隔离方案
venv 是 Python 标准库内置的工具,它的痛点非常明确:解决全局环境污染。如果不使用虚拟环境,所有包都装在系统目录下,不同项目对同一个包的版本需求冲突时,环境就会崩溃。
操作指南
1 | # 1. 创建虚拟环境 (通常命名为 .venv 或 venv) |
原理解析:sys.path 的魔法
你可能会好奇,激活虚拟环境到底做了什么?其实它只是修改了环境变量 PATH。
当虚拟环境激活后,Shell 会优先使用 .venv/bin/python。当这个解释器启动时,它会根据自身的路径,动态修改 sys.path(Python 导入模块的搜索路径列表)。
- 未激活时:
sys.path指向系统的site-packages。 - 激活后:
sys.path指向.venv/lib/pythonX.X/site-packages。
这就是为什么激活后pip install的包会乖乖待在.venv里,不会污染系统。
2.2 环境复现的进化
早期:requirements.txt 的局限
传统的做法是使用 pip freeze:1
2pip freeze > requirements.txt
pip install -r requirements.txt
痛点:pip freeze 导出的列表是“扁平”的,它混杂了直接依赖(你项目真正用到的包)和间接依赖(依赖的依赖)。这导致维护极其困难——你不敢随便删包,因为不知道它是谁引用的。
现代:pyproject.toml 与可编辑安装
现代 Python 工程推荐使用 pyproject.toml 来声明项目元数据和直接依赖。
操作步骤:
- 编写
pyproject.toml(声明依赖,如dependencies = ["requests"])。 - 在项目根目录下,使用 pip 进行可编辑安装:
1 | # -e 表示 editable,. 表示当前目录 |
原理:这会将当前项目以“软链接”的形式安装到 site-packages 中。此时,pip 会自动解析 pyproject.toml,安装所需的依赖树。这让项目结构更清晰,且区分了开发依赖和生产依赖。
3. 极速现代化:uv 的降维打击
venv 和 pip 虽然稳健,但速度慢、操作繁琐(必须手动激活)。
uv 是一个由 Rust 编写的现代 Python 包管理工具,它的定位是:pip + venv + poetry 的超快替代品。
核心功能与操作
1. 自动管理环境 (uv add)
你不再需要手动创建和激活 venv,uv 会自动处理。
1 | # 初始化项目 |
2. 锁定与同步 (uv sync)
多人协作时,uv.lock 文件锁定了所有包的精确版本(包括间接依赖的哈希值),确保“在我的机器上能跑”等于“在你的机器上也能跑”。
1 | # 在新机器上,根据 lock 文件一键还原环境 |
3. 运行脚本 (uv run)
这是最爽的功能。你不需要 source .venv/bin/activate,直接运行:1
2# uv 会自动在虚拟环境中执行 main.py
uv run main.py
原理:
uv底层通过极其激进的缓存策略和 Rust 的并发能力,将解析依赖和下载安装的速度提升了 10-100 倍。它也遵循标准,生成的.venv是标准的,可以被 IDE 识别。
4. 科学计算的高塔:Conda 生态
如果你的项目涉及大量 C/C++ 扩展(如 NumPy, PyTorch, GDAL),pip 有时编译会报错。这时候,Conda 体系是最佳选择。Conda 不仅管理 Python 包,它管理的是二进制库,甚至可以管理 Python 解释器本身的版本。
4.1 家族成员大盘点
很多初学者容易被这一堆名词搞晕,我们来从重到轻梳理:
- Anaconda Distribution:
- 定位:全家桶。预装了上百个科学计算包和图形界面。
- 缺点:体积巨大(几个GB),且商业使用收费。
- Miniconda:
- 定位:最小化安装器。只包含
conda命令和 Python。 - 缺点:默认使用的是
defaults频道(Anaconda 公司的源),部分包可能涉及授权问题。
- 定位:最小化安装器。只包含
- Conda-forge:
- 定位:社区维护的频道(Channel)。包最新、最全,且完全免费开源。
- 使用:
1
2# 安装时指定频道
conda install numpy -c conda-forge
- Miniforge:
- 定位:Miniconda 的社区版替代品。
- 特点:默认配置
conda-forge为下载源,彻底规避商业授权风险。强烈推荐。
4.2 速度革命:Mamba
Conda 是用 Python 写的,解决依赖关系(Solver)时非常慢,尤其是环境复杂时。
Mamba 是 Conda 的 C++ 重写版本,命令参数与 Conda 完全一致,但速度快得多。
1 | # 使用 mamba 替代 conda |
4.3 Best Practice:Miniforge + Mamba
目前科学计算领域的最佳实践方案如下:
- 下载安装 Miniforge。
- Miniforge 会默认安装
mamba命令。 - 日常使用:
1
2
3
4
5
6
7
8# 创建环境 (指定 Python 版本)
mamba create -n my-env python=3.10
# 激活环境 (注意:激活还是用 conda/source)
conda activate my-env # 或者 source activate my-env
# 安装包 (使用 mamba 加速)
mamba install pandas scikit-learn
5. 进阶:Python 项目结构与打包
当你写好了代码,如何把它变成一个别人可以用 pip install 安装的包?
5.1 whl 文件是什么?
whl (Wheel) 是 Python 的标准分发格式。它本质上是一个 Zip 压缩包,里面包含了:
- 你的代码文件 (
.py/.pyc) - 元数据 (
.dist-info):包含版本号、依赖列表等。
相比源码包(sdist),Wheel 安装更快,因为它是预构建的(pre-built)。
5.2 推荐的项目结构:Src Layout
早期的项目结构是扁平的(Flat Layout),但现在推荐 Src Layout。
推荐结构:1
2
3
4
5
6
7
8my-project/
├── pyproject.toml # 核心配置文件
├── README.md
├── src/ # 源码隔离层
│ └── my_package/
│ ├── __init__.py
│ └── main.py
└── tests/ # 测试代码
为什么用 src 目录?
- 痛点:在扁平结构中,运行测试时,Python 可能会错误地
import当前目录下的文件夹,而不是安装在site-packages里的包。 - 原理:
src目录本身不是一个包,这强迫你在开发时必须正确安装包(pip install -e .)才能运行代码,从而避免了“开发环境能跑,打包后不能跑”的尴尬。
5.3 打包流程
在 pyproject.toml 标准化后,打包变得非常简单。主要涉及两个概念:
- Frontend (构建前端):如下令者,如
build命令。 - Backend (构建后端):如干活者,常用的有
setuptools,hatchling,flit。
实战打包代码:
- 安装构建工具:
1
pip install build
- 在项目根目录运行:
1
python -m build
- 结果:会在
dist/目录下生成.whl文件和.tar.gz源码包。
总结
Python 的包管理虽然复杂,但脉络已逐渐清晰:
- Web/脚本/通用开发:请拥抱 uv。它既兼容标准(
pyproject.toml,venv),又提供了极致的速度和体验。 - 数据科学/深度学习:请锁定 Miniforge (Mamba)。这是最快、最省心且无版权风险的方案。
- 工程化:务必使用 Src Layout 和 pyproject.toml,告别
requirements.txt的混乱时代。
希望这篇指南能助你在 Python 的开发之路上少踩几个坑!
疑问
1. 如果不激活虚拟环境,对于pip来说就是使用site-packages中的包,而conda则使用base中的包吗;conda base环境是不是不需要特意激活,为什么会这样?
这里涉及到一个核心概念:环境变量 PATH。
对于 pip / 原生 Python:
- 如果不激活虚拟环境:终端里的
python命令会去PATH变量里排在最前面的路径寻找解释器。通常这会指向你操作系统安装的那个 Python(我们称为 System Python)。- 此时执行
pip install,包会被安装到系统层级的site-packages目录中。 - 风险:这非常危险,容易搞乱系统(比如在Linux上可能搞挂依赖Python的系统工具如
yum或apt的某些功能)。
- 此时执行
- 激活虚拟环境做了什么:它仅仅是把虚拟环境的
/bin(Linux/Mac) 或/Scripts(Windows) 路径插入到了PATH变量的最前面。
对于 Conda 与 Base 环境:
- Base 环境需不需要特意激活?
- 通常不需要手动激活。当你安装 Anaconda/Miniconda 并运行了
conda init后,它会修改你的 Shell 配置文件(如.bashrc或 PowerShell Profile)。 - 默认行为:每次打开终端,Conda 会自动帮你激活
base环境。你会看到命令行前面有个(base)。 - 为什么这样? 因为
conda这个命令本身、以及一系列基础科学计算包都装在 base 环境里。Conda 希望接管你的 Python 环境,让你默认使用它的 Python,而不是系统的 Python,从而避免系统兼容性问题。
- 通常不需要手动激活。当你安装 Anaconda/Miniconda 并运行了
- 如果不激活 Conda (或退出了 base):
- 此时输入
python,使用的就是操作系统的 Python。 - 此时输入
conda install,虽然命令可用(因为conda程序路径还在),但如果不指定环境,行为会比较混乱,或者提示你需要激活环境。
- 此时输入
2. 依赖冲突:如果一个项目在pyproject.toml中写了要使用某个依赖(比如numpy),而系统中也有这个包,那最终会导致电脑中有两份numpy吗
简短回答:是的,通常会有两份,甚至更多份。
详细场景解析
假设你的电脑结构如下:
- 系统/Base环境:已经安装了
numpy 1.21。 - 项目A (使用 venv):
pyproject.toml里声明依赖numpy。
当你为项目A创建虚拟环境并安装依赖时:
- 默认行为(隔离优先):
pip会下载并安装一份新的numpy到项目A的虚拟环境目录中(例如.venv/lib/python3.x/site-packages/numpy)。 - 结果:你的硬盘上有两份 numpy。
为什么要这样设计(浪费空间)?
这是为了绝对的隔离。
- 如果项目A依赖
numpy 2.0(最新版),而系统里只有numpy 1.21。如果复用系统的,项目A就会报错。 - 反之,如果项目A强制把系统的升级到 2.0,那么依赖 1.21 的其他脚本(或系统工具)就会崩溃。
- 结论:硬盘空间很便宜,但排查版本冲突的时间很昂贵。
现代工具的优化 (uv 的黑科技)
我在上一篇文章里提到的 uv 或 pdm 这类现代工具,引入了 Content-addressable storage (内容寻址存储) 机制:
- 它们会在全局有一个缓存池。
- 当项目A需要
numpy时,它们会先看缓存池里有没有。 - 如果有,它们通过 硬链接 (Hard Link) 或 Reflink 的方式,把文件“映射”到虚拟环境中。
- 效果:逻辑上是两份(完全隔离),但物理上在硬盘里只占一份空间。
3. pip install -e . 到底做了什么?
这个命令是 Python 开发的神技,全称是 Editable Install(可编辑安装)。
普通安装 (pip install .) vs 可编辑安装 (pip install -e .)
普通安装:
- Pip 会把你的代码复制一份,打包,然后解压扔到
site-packages目录下。 - 后果:你修改了你项目里的源代码,Python 跑的还是
site-packages里那份旧的复制品。你必须重新安装才能生效。
- Pip 会把你的代码复制一份,打包,然后解压扔到
可编辑安装 (
-e) 的魔法原理:- 它不会复制你的代码文件。
- 它会在
site-packages目录下生成一个.pth文件(例如my-project.pth)。 .pth文件的内容:只有一行字,就是你项目源代码的绝对路径(例如D:\Code\MyProject\src)。- Python 启动时:Python 解释器启动时会扫描
site-packages下所有的.pth文件,并把这些文件里记录的路径,临时添加到sys.path(模块搜索路径)中。
实际效果
当你 import my_package 时,Python 顺着 .pth 的指引,直接跑去你的源代码目录 D:\Code\MyProject\src 里加载代码。
- 好处:你改了代码,Ctrl+S 保存,下一秒运行就是最新的逻辑,无需重新安装。这对开发阶段至关重要。
4. 我听有一种说法说现在python官方的包管理工具(比如pip)已经比较好地解决了跨语言兼容问题,是这样的吗,它实现的 方法也类似conda吗?
结论:是的,Pip 确实变强了,但它和 Conda 的实现路径完全不同。
早期的 pip install numpy 经常失败,因为通过 pip 下载的是源码包(Source Distribution, sdist),需要你本地有 C/C++ 编译器(gcc/MSVC)现场编译。
Pip 的解决方案:Wheel (.whl) 与“自带干粮”
现在的 Pip 之所以能顺畅安装 Numpy、PyTorch,是因为它引入了 Wheel 二进制格式,以及 Manylinux 标准。
原理:
- 包的开发者在服务器上提前编译好代码。
- 关键点:他们把所有依赖的 C 语言动态链接库(.dll 或 .so 文件),全部打包塞进了这个
.whl文件里。 - 当你
pip install时,实际上是在解压一个自带了所有依赖库的压缩包。
与 Conda 的区别(关键!):
- Pip (Wheel) 是“静态打包”思想:假设安装 PyTorch。Pip 下载的 whl 包里可能已经硬塞进了一个特定版本的 CUDA 运行时库(或者它假设你系统里有)。这就导致 Pip 包通常体积巨大(因为每个包都可能自带一份重复的基础库)。
- Conda 是“动态环境”思想:Conda 把 C 语言基础库(如 CUDA、MKL、OpenSSL)也视为“包”。当你安装 PyTorch 时,Conda 会检测环境,如果没有 CUDA,它会单独下载 CUDA 包安装到环境中,供 PyTorch 调用。
局限性:
Pip 虽然解决了 90% 的 Python 库问题,但在涉及非 Python 工具链时依然无能为力。- 例子:如果你需要安装
ffmpeg(视频处理工具)或gdal(地理信息系统),Pip 做不到(因为它只管 Python 包),而 Conda 可以直接conda install ffmpeg,因为它是一个全能的包管理器。
- 例子:如果你需要安装
5. 终极对比:Conda vs venv
为了方便记忆,我们可以用装修房子来打比方:
- venv:像是在你现有的房子里(系统 Python)拉了一个隔断。
- Conda:像是直接在空地上盖了一栋新房子。
核心差异对比表
| 维度 | venv (搭配 pip) | Conda |
|---|---|---|
| 管理对象 | 只能管理 Python 包 | 管理 Python包 + 二进制库 + Python解释器本身 |
| Python版本 | 依赖宿主。你系统是 Python 3.8,生成的 venv 就是 3.8,无法更改。 | 独立自主。系统是 3.8,你可以创建 3.9、3.10 甚至 2.7 的环境。 |
| 创建原理 | 软链接。复用系统 Python 的核心文件,只复制少量必要文件。轻量级。 | 硬拷贝/独立安装。下载并安装一套完整的 Python 解释器和基础库。重量级。 |
| 非Python依赖 | 无能为力(需自行安装系统软件,如 apt-get install)。 |
全能(可安装 Node.js, R, C++ 编译器, CUDA 等)。 |
| 适用场景 | Web 开发、脚本自动化、云原生部署(Docker 中首选)。 | 数据科学、机器学习、学术研究、不想折腾环境配置的新手。 |
总结与建议
如果你是做 Web 开发(Django/FastAPI):
- 请用 venv (或者更现代的
uv)。 - 因为服务器环境通常是用 Docker 部署的,Docker 容器本身就是隔离环境,再套一层 Conda 显得臃肿且多余。
- 请用 venv (或者更现代的
如果你是做 深度学习/数据分析:
- 请用 Conda (推荐 Miniforge)。
- 因为你需要频繁切换 Python 版本(比如某个老代码只跑在 3.7),且需要安装大量复杂的科学计算库(它们之间的依赖关系非常脆弱,Conda 的求解器能救命)。
希望这个对比能帮你彻底理清两者的定位!
6. uv解决跨语言兼容的问题的方法类似pip吗
结论:是的,几乎完全一致。uv 继承了 pip 的“Wheel 生态”红利。
为了理解这一点,我们需要区分 “包管理器(Manager)” 和 “包格式(Format)”。
1. 核心机制:站在巨人的肩膀上
uv 本质上是一个用 Rust 重写的、速度极快的 Installer(安装器) 和 Resolver(解析器)。它并没有发明新的包格式,它和 pip 一样,主要从 PyPI (Python Package Index) 下载安装包。
- pip 的方法:下载
.whl(Wheel) 文件。如前文所述,现代 Wheel 文件里已经通过 Static Linking (静态链接) 或 Bundling (打包) 的方式,内置了编译好的 C/C++/Rust 动态库(.so或.dll)。 - uv 的方法:
uv做的也是同样的事。它去 PyPI 下载同样的.whl文件,解压到虚拟环境里。
因此,如果一个包(如 NumPy)提供了适配你系统的 Wheel 文件,pip 能装,uv 就能装;如果 pip 装不上(需要编译),uv 通常也装不上。
2. uv 相比 pip 的微小差异
虽然大逻辑一样,但 uv 在处理兼容性时更加“严格”且“智能”:
- 预构建二进制优先:
uv会极度优先寻找预编译好的二进制包(Binary Wheels)。如果找不到,默认情况下它可能直接报错或跳过,而不是像 pip 那样尝试在本地拉起 GCC 编译器去编译源码(这是 pip 慢且容易报错的一大原因)。当然,你可以通过参数强制uv进行编译,但它更推崇“开箱即用”。 - 跨平台解析:
uv支持在 Linux 机器上生成适用于 Windows 的uv.lock文件。它能读取不同平台的 Wheel 元数据,确保团队协作时的兼容性。
3. uv vs Conda
这里再次强调,uv 是 Python 生态 的工具,它解决跨语言兼容靠的是 Wheel 文件的自包含。
- Conda:可以直接安装
gcc,cuda,ffmpeg这种非 Python 软件到环境里。 - uv:只能安装 Python 包。如果你的 Python 包依赖系统级的
ffmpeg,uv 没法帮你装ffmpeg,你还是得去系统里装。
7. 一个现代的python项目中一般有哪些内容,详细解释(尤其是会有site-packages吗,site-packages里有什么)
这是一个非常好的问题,很多新手会将“源代码”和“运行环境”混淆。
核心原则:现代 Python 项目中,源代码(Code)与环境(Environment)必须严格分离。
1. 现代项目目录结构图(Standard Src Layout)
一个标准的、符合现代工程规范(基于 pyproject.toml)的项目结构如下:
1 | my-project/ |
2. 关键文件/文件夹深度解析
A. pyproject.toml (项目的身份证)
这是现代 Python 项目的唯一真神。它取代了以前的 setup.py, requirements.txt, setup.cfg。
- 作用:声明项目名字、版本、作者,以及直接依赖(Dependencies)。
- 内容示例:
1
2
3
4
5
6[project]
name = "my-project"
dependencies = [
"numpy>=1.21",
"requests"
]
B. uv.lock (或者 poetry.lock)
- 作用:这是给机器看的,不是给人写的。
- 原理:当你安装依赖时,工具会计算出所有依赖(包括依赖的依赖)的确切版本号和哈希值,记录在这里。
- 价值:确保你同事电脑上装的包,和你电脑上的一模一样,连哈希值都对得上。
C. src/ 目录 (Src Layout)
- 为什么不直接把代码放在根目录?
如果放在根目录(比如my_package直接在my-project下),当你运行pytest时,Python 可能会错误地直接import当前文件夹里的代码,而不是你安装到环境里的代码。使用src/强制你必须先安装包(pip install -e .)才能运行,这能避免很多“开发环境能跑,打包后报错”的 Bug。
3. 直击灵魂:site-packages 到底在哪?里边有什么?
Q: 项目文件夹里会有 site-packages 吗?
A: 物理上有,逻辑上没有。
- 物理上:它位于
.venv文件夹深处。路径通常是.venv/lib/python3.x/site-packages。 - 逻辑上:它绝对不属于你的项目源代码。必须在
.gitignore文件中把.venv/忽略掉。千万不要把site-packages提交到 Git 仓库里!
Q: site-packages 里面具体有什么?
这是一个包含了所有第三方库的大杂烩。假设你安装了 numpy 和你自己的项目(可编辑模式),里面会有:
真正的第三方包代码:
- 文件夹
numpy/:这是你import numpy时真正加载的代码。 - 里面包含
.py文件,以及编译好的.so或.pyd(C扩展库)。
- 文件夹
元数据文件夹 (
.dist-info):- 例如
numpy-1.24.0.dist-info/。 - 这里面记录了包的作者、License、安装了哪些文件(RECORD文件)、入口点(Entry Points)等信息。
pip list命令就是靠读取这些文件夹来显示列表的。
- 例如
可编辑安装的链接文件 (
.pth):- 如果你运行了
uv add .或pip install -e .。 - 你会发现一个
my-project.pth文件。 - 内容:只有一行文本,指向你的
D:/Projects/my-project/src。 - 作用:告诉 Python,“虽然我在 site-packages 里,但请去 src 目录找代码”。
- 如果你运行了
总结
- uv 靠的是 PyPI 现成的 Wheel 生态来解决跨语言兼容,这点和 pip 一脉相承。
- 现代项目 =
pyproject.toml(蓝图) +src/(源码) +.venv(施工现场)。 - site-packages 是施工现场的一部分,只存在于
.venv中,严禁提交到 Git。
示例
我们在理论上知道了“虚拟环境可以隔离依赖”,但通过实际操作眼见为实,才能真正理解底层的 sys.path 魔法。
本文将带你通过一个 5 分钟的微型实验,验证以下两个核心原理:
- 环境切换原理:激活 venv 后,Python 解析器的路径发生了什么变化?
- pip 工作原理:pip 是怎么知道要把包安装到虚拟环境而不是系统目录的?
1. 实验准备
首先,在你的电脑上创建一个空的实验目录。我们将从零开始。
1 | # 1. 创建项目目录 |
请在 inspect_env.py 中写入以下代码。这个脚本将充当我们的“探针”,打印出当前 Python 到底是谁,以及它会去哪里找包。
1 | # inspect_env.py |
2. 阶段一:裸奔状态(全局环境)
在没有创建虚拟环境之前,我们先运行一下侦探脚本,看看系统环境是怎样的。
操作命令:1
python inspect_env.py
预期输出(示例):1
2
3
4
5
6
7
8--- 环境侦探报告 ---
[1] 当前 Python解释器路径:
/usr/bin/python3 <-- 指向操作系统自带的 Python
[2] 模块搜索路径:
/usr/lib/python3.10
...
[3] 尝试导入 requests 包:
失败! (或者如果你系统里装了,会显示系统路径)
原理分析:此时终端使用的是环境变量
PATH中优先级最高的python,通常是系统级的。
3. 阶段二:创建并激活 venv
现在我们创建一个隔离环境。
操作命令:
1 | # 1. 创建名为 .venv 的虚拟环境 |
激活成功后,你的命令行提示符前应该会出现 (.venv) 字样。
再次运行侦探脚本:1
python inspect_env.py
预期输出(关键变化):1
2
3
4
5
6--- 环境侦探报告 ---
[1] 当前 Python解释器路径:
/Your/Project/venv_lab/.venv/bin/python <-- 【变化点】指向了项目目录内!
[2] 模块搜索路径:
/Your/Project/venv_lab/.venv/lib/python3.x/site-packages <-- 【变化点】排在了前面
...
原理分析:
激活脚本(activate)修改了当前 Shell 的PATH变量,把.venv/bin塞到了最前面。
当你输入python时,Shell 找到了虚拟环境里的那个 Python。这个 Python 启动时,会自动把自己的site-packages目录加入到sys.path中。
4. 阶段三:pip 安装实战
关键时刻来了。在这个激活的环境下,我们使用 pip 安装一个特定版本的包。
操作命令:1
2# 故意安装一个老版本,方便区分
pip install requests==2.25.1
验证安装结果:1
python inspect_env.py
预期输出:1
2
3
4...
[3] 尝试导入 requests 包:
成功! 安装位置: /Your/Project/venv_lab/.venv/lib/python3.x/site-packages/requests
版本: 2.25.1
Pip 的工作原理:
为什么pip知道要装到.venv里?
- 当你激活环境后,输入
pip,实际上运行的是.venv/bin/pip。- 这个
pip是一个 Python 脚本,它的第一行 Shebang (#!/...) 明确指向了.venv/bin/python。- 因此,
pip install本质上就是用虚拟环境里的 Python 去运行安装程序,自然就会把包解压到该 Python 对应的site-packages目录下。
5. 阶段四:退出与隔离验证
最后,我们要验证“隔离性”。如果我们退出虚拟环境,刚才安装的 requests 还在吗?
操作命令:1
2
3
4
5# 退出虚拟环境
deactivate
# 再次运行侦探脚本
python inspect_env.py
预期结果:
- 解释器路径变回了系统路径(如
/usr/bin/python)。 requests导入失败(或者变成了系统里的其他版本)。
总结
通过这个实验,我们证实了以下逻辑链条:
- activate 修改了
PATH环境变量。 - python 命令因此指向了
.venv内部的解释器。 - .venv 解释器 启动时,将
.venv/lib/.../site-packages加入 sys.path。 - pip 命令本质是绑定了该解释器的脚本,所以它下载的包,最终落入了上述的
site-packages中。
这就是 Python 虚拟环境“欺骗”操作系统、实现依赖隔离的全部秘密。