1. 为什么你绕不开 Python 虚拟环境
Python 开发者十有八九都经历过这种场景:昨天还在跑的 Django 项目,今天升级了某个依赖之后,报错报得莫名其妙。更离谱的是,同一个项目在两台机器上表现完全不同,一台跑得欢,另一台直接启动失败。如果你也踩过这种坑,那你大概率还没有真正用熟虚拟环境。
简单说,虚拟环境就是给 Python 项目单独隔出来的一个“房间”,房间里装着你这个项目需要的 Python 解释器、第三方库和脚本工具,关起门来自己玩,不跟系统全局环境或别的项目互相干扰。你在 A 项目里把 requests 从 2.25 升到 2.31,丝毫不会影响 B 项目里依赖 requests 2.25 的代码。这个隔离能力,是正经 Python 项目开发的地基,不管你是写爬虫、做数据分析还是搞 Web 后端,早晚都得掌握。
这篇文章我不会只丢给你几条命令就完事。整个流程我会拆开揉碎,讲清楚虚拟环境的核心原理、几种主流创建方式的对比、实操步骤、迁移技巧以及我这些年踩过的坑。内容偏向实战,新手可以直接照做,老手也能在某些细节上查漏补缺。
2. 虚拟环境的核心原理与工具选型
2.1 虚拟环境解决了什么问题
虚拟环境解决的,本质上是 Python 包管理里的“依赖冲突”和”全局环境污染”两大问题。用生活化的说法:一个 Python 解释器就像一套房子,你装新依赖就是往房子里添家具。每天添一点,房子的空间和摆设就被占得满满的,有些旧家具和新家具还会互相挤。虚拟环境相当于给每个项目都租了一套独立拎包入住的公寓,项目结束后整个公寓可以随手丢掉,新房客再租一套全新的,旧家具碰都碰不着。
从技术原理上讲,绝大多数虚拟环境工具做的事情非常简单:复制一份 Python 解释器入口,然后创建一套独立的 site-packages 目录,再用环境变量(比如 PATH、PYTHONPATH、VIRTUAL_ENV)把当前终端会话的指向”骗“到这套新目录上去。这样一来,pip 安装的包就会进入虚拟环境自己的 site-packages,而不会污染全局。
2.2 venv、virtualenv 与 conda 怎么选
你在网上搜虚拟环境教程,会发现信息非常乱,venv、virtualenv、conda、uv、Pipenv 各种方案满天飞。我根据自己这些年来的实际使用体验,给你整理一个清晰的对比表格。
| 工具 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| venv | Python 3.3+ 自带的虚拟环境模块,日常首选 | 零依赖,Python 安装即自带,操作简单,够用 | 不支持 Python 2;创建速度一般;无法直接管理不同 Python 版本 |
| virtualenv | 老项目/需要兼容 Python 2,或需要更细粒度控制 | 功能比 venv 全,支持 Python 2,可指定解释器版本 | 需要单独 pip 安装,多了一层维护成本 |
| conda | 数据科学、需要非 Python 语言依赖的环境 | 跨语言依赖管理强,环境可指定任意 Python 版本 | 安装包体积大,环境管理与包管理耦合,上手稍重 |
| uv | 需要极快环境创建/依赖安装的现代 Python 项目 | 极快,Rust 编写,兼容 requirements.txt,工具链统一 | 相对年轻,生态还在完善中,部分老项目兼容性待验证 |
从我个人的项目经验来看,纯 Python 项目直接用 venv 就足够了,没必要再装 virtualenv。如果你主要做数据分析或者机器学习,机器上装了 Anaconda,那就老实用 conda 创建环境,因为很多科学计算包(像 numpy、scipy、pandas)在 conda 里面有预编译好的二进制,装起来又快又不容易出幺蛾子。uv 我最近也在尝试,适合新项目和新机器,速度是真的快,但如果是公司存量项目我暂时不会轻易切换。
注意:venv 和 virtualenv 创建的虚拟环境,默认不会包含系统级 site-packages 里的包。如果你希望虚拟环境能“看到”全局已安装的某些大包(比如 PyTorch),可以加
--system-site-packages参数。这个参数我在实际项目里经常结合使用,建议你了解一下,但默认别开。
2.3 工具选型的决策思路
选型这件事,核心不是“哪个工具最强”,而是“哪个工具最适合你的项目、团队和运维习惯”。我见过有人为了用 conda 管理环境,硬把一个纯 Flask 项目从 venv 迁移过去,结果白白折腾了半天,收益几乎为零。反过来,也有人用 pip 硬装一堆 CUDA 相关依赖,最后被版本搭配问题搞得焦头烂额,早知如此当初就该用 conda。
我的建议是:把选型规则固定下来,不要每次新建项目都重新纠结。比如我自己的默认策略是——纯 Python 项目一律python -m venv,数据科学项目用 conda,涉及多种语言或需要固定 Python 版本的 CI/CD 场景也优先 conda,新工具(比如 uv)先在个人玩具项目里验证再引入生产。
3. 基于 venv 创建虚拟环境的完整实操
3.1 环境准备与版本检查
在开始之前,先确认你机器上的 Python 版本。Python 3.3 以上都内置了 venv 模块,但实际使用中我更推荐 3.9 以后的版本,因为虚拟环境相关的处理逻辑(比如对 site-packages 的处理、pip 的默认行为)在一些旧版本上存在各种坑。
打开终端,运行:
python --version如果你在 Windows 上用的是 py 启动器,也可以这样查:
py -3 --version看到版本输出之后,顺手确认 pip 可用:
python -m pip --version不建议直接敲pip --version,因为某些机器上pip指向的可能是另一个 Python 环境,而python -m pip才是百分百对应当前解释器。
3.2 创建虚拟环境的标准流程
假设我要为一个博客项目创建一个名为blog_env的虚拟环境,项目代码放在/home/user/projects/blog目录下。全过程如下:
cd /home/user/projects/blog python -m venv blog_env这一步做完,你会发现当前目录下多了一个blog_env文件夹。Linux 和 macOS 下它里面包含bin、lib、include等子目录,Windows 下则是Scripts、Lib、Include。bin或Scripts里放着可执行的 python、pip;lib则对应 Python 的库目录,往后你往环境里装的所有第三方包都会被放到这里。
激活虚拟环境是第二步。这一点新手最容易出错,我每次带人也都会特意强调:不激活就用,等于没用虚拟环境。
Linux 和 macOS 下激活:
source blog_env/bin/activateWindows 下激活:
blog_env\Scripts\activate成功激活之后,命令行提示符前面会出现(blog_env)的前缀,比如:
(blog_env) user@host:~/projects/blog$看到这个前缀,恭喜你,你的命令现在都在虚拟环境里执行了。
安装依赖。激活之后,你就可以用pip安装这个项目需要的任何包了:
pip install flask requests gunicorn这些包会被安装到虚拟环境自己的库里,全局环境不受任何影响。
退出虚拟环境。当你项目忙完,回到终端后,只需要一条命令就能离开这个环境:
deactivate提示:在 Windows PowerShell 下,如果系统提示“无法加载脚本,因为在此系统上禁止运行脚本”,这是 PowerShell 的执行策略限制。解决方案是先用
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser把策略放开,或者直接改用cmd运行 activate 脚本,没必要跟执行策略死磕。
3.3 为什么要在项目目录内创建虚拟环境
你可能会注意到我把虚拟环境建在了项目目录内部(/home/user/projects/blog/blog_env)。这个做法有一个非常实际的好处:查找和管理方便。你只要打开项目目录,就知道这个环境是给谁用的;清理项目的时候,删掉整个目录,环境也跟着消失,干净利落。
但也有人把环境建在统一的环境目录下,比如~/venvs/blog_env。这种做法的优势是项目目录不显得乱,尤其是在你使用 Git 管理项目时,不需要在.gitignore里额外屏蔽它们。两种都没有对错,我只是建议你把规则固定下来。
如果你选择项目内建环境,别忘了在.gitignore中加上环境目录名。我见过不止一次,有新人把 100MB 的虚拟环境连同代码一起推到 Git 仓库,导致仓库膨胀、clone 极慢。如果你想避免这种尴尬,.gitignore里至少写上这样一行:
blog_env/ .venv/ env/ venv/3.4 requirements.txt 的生成与依赖锁定
虚拟环境创建好之后,最核心的配套操作就是生成依赖清单。有了这份清单,换台机器或部署到服务端时,你就能瞬间重建一模一样的环境。
生成依赖清单:
pip freeze > requirements.txt重建环境时,其他机器上只需要:
python -m venv blog_env source blog_env/bin/activate pip install -r requirements.txt这里我说一个比较关键的经验:pip freeze会把环境里所有已安装的包全部列出来,包括 A 依赖 B、B 依赖 C 这种间接依赖也全在里面。这带来的问题是,你在生产环境装包时可能会因为版本锁定得太死而装不上。更好的做法是使用pip-tools这类工具,维护一份requirements.in(只写直接依赖和大致版本约束),再编译生成可复现的requirements.txt。
如果你嫌多学一个工具麻烦,那至少要做到:锁定顶层依赖的版本号,不要用pip install flask这种裸装,而是pip install flask==3.0.0,然后把pip freeze的结果作为基础模板,自己审一遍再提交。
4. 使用 conda 创建虚拟环境
4.1 为什么 conda 在某些场景下不可替代
刚才在选型对比里提过,如果你主要做数据科学、机器学习,Anaconda 大概率是你的默认 Python 发行版。conda 的虚拟环境能力在数据科学领域几乎是必备技能,因为像 TensorFlow、PyTorch 这些框架的 CUDA 版本依赖极其复杂,单纯用 pip 管理很容易把系统搞崩。
conda 创建虚拟环境时,不仅会隔离 Python 包,还能隔离 Python 解释器本身、系统库级的依赖(比如 MKL、OpenMP 这些底层数值计算库)。这就是它跟 venv 最大的本质区别——venv 虚拟环境必须基于你当前机器上某个已存在的 Python 版本创建,而 conda 可以直接下载并安装一个独立的 Python 解释器版本到环境里。
4.2 conda 创建环境的标准流程
首先检查 conda 是否就绪:
conda --version创建一个名为ml_lab的环境,指定 Python 3.10:
conda create -n ml_lab python=3.10如果你需要装 nginx、cmake 这类非 Python 工具链到环境里,也可以直接在创建时指定,比如:
conda create -n ml_lab python=3.10 cmake nginx激活环境和退出环境分别是:
conda activate ml_lab conda deactivate查看当前所有环境以及当前环境所在路径,用:
conda env list这个命令我每周至少用一次,它会输出每个环境对应的完整路径。有时候你项目里明明有包依赖问题,排查了半天才发现是激活错了环境,路径一看清清楚楚。
4.3 conda 环境导出与迁移
conda 最贴心的一点,是环境导出能力很强。假设你在本地把环境打磨得很完美,要部署到服务器上,直接执行:
conda env export > environment.yml在服务器上导入环境:
conda env create -f environment.yml还有一个很实用的导出子集:
conda env export --from-history > environment.yml--from-history这个参数只会导出你显式安装的包,不会包含那些通过依赖关系自动装进来的包。拿到的一瞬间你可能觉得清单不够完整,但这恰恰是它的优点——重建时只装核心依赖,让 conda 自己解析依赖树,兼容性和成功率都更高。
注意:conda 导出的
environment.yml里会包含prefix字段,指向你本地环境的绝对路径。别人拿到这份文件导入时会警告路径不匹配,一般不影响使用,但如果你想传递给别人用,建议手动把prefix那行删掉。
5. 虚拟环境的迁移、复制与换机技巧
5.1 开发机换到新电脑的场景
做开发的人换电脑最痛苦的不是硬件适应,而是环境迁移。我概括一下虚拟环境迁移的三种常见路径,你按需选用。
路径一:requirements 文件重建(最推荐)
这是我在绝大多数项目上的迁移方案。核心逻辑:环境本身不跨机器拷贝,只迁移依赖清单。优点是干净、可控、可审计;缺点是如果依赖里有本地构建的包(比如某些只有源码的库),新机器上可能需要额外安装编译工具链。
# 旧机器导出 python -m venv my_env source my_env/bin/activate pip freeze > requirements.txt # 新机器重建 python -m venv my_env source my_env/bin/activate pip install -r requirements.txt路径二:直接拷贝环境目录(小范围可用)
如果你换的是同一台机器上的两个用户目录,或者两台机器操作系统完全相同、Python 版本也完全一致,那直接把my_env目录打包拷过去也能用。但它隐含的风险很大:有些包在安装时写入的是绝对路径,比如某个包在site-packages/xxx.pth里写死了/home/user/projects/blog/my_env/lib/python3.10/site-packages,换了路径目录就跑不起来。
路径三:使用 conda-pack 打包环境
conda 环境的便携迁移可以借助conda-pack,它能将环境打包成单个压缩文件,目标机器无需安装 conda 也能解压使用。适合离线部署场景。
pip install conda-pack conda-pack -n my_env -o my_env.tar.gz在目标机器上:
mkdir -p ~/my_env tar -xzf my_env.tar.gz -C ~/my_env source ~/my_env/bin/activate这个方法我强烈建议数据科学团队存一份到内部文档里。模型跑起来之后,你想把整套代码和依赖放到一台没有外网的内网服务器上,conda-pack几乎是你唯一的简单方案。
5.2 迁移时最容易翻车的三个细节
第一,不同操作系统之间不要直接拷贝环境,即使 Python 版本一样都不行。因为很多扩展包是编译成二进制共享库的,Windows 上是.pyd,Linux 上是.so,两者完全不通用。
第二,pip 版本差异会导致安装失败。旧机器 pip 是 23.0,新机器默认 pip 是 20.0,解析 requirements.txt 的方式可能不同。迁移时建议先在虚拟环境里升级 pip 再安装:python -m pip install --upgrade pip。
第三,如果 requirements.txt 里有 Git 仓库安装的包,比如pip install git+https://github.com/xxx/repo.git@tag,那么新机器上必须能访问这些 Git 仓库。离线环境或者内部网络不部署 GitLab 的话,这条很容易导致安装中断,我在实际项目里就吃过这个亏。
6. 常见问题与排查技巧实录
6.1 常见问题速查表
我长期在社区回答 Python 环境问题,下面这四类问题占比可能超过七八成。整理成速查表,方便你按图索骥。
| 问题现象 | 根本原因 | 解决方案 |
|---|---|---|
激活虚拟环境后,python还是全局版本的 | 没有正确激活,或 shell 缓存了旧的命令路径 | 检查which python路径,确认 activate 脚本执行成功;重新打开终端再激活 |
pip install提示“externally-managed-environment” | 系统 Python 的新安全机制,限制 pip 全局写入 | 使用虚拟环境再装;或给 pip 加--break-system-packages(不推荐) |
| 虚拟环境目录太大,Git 提交非常慢 | 环境目录未添加到.gitignore | 在.gitignore中加入venv/、*.env/等规则 |
| Windows 激活报错“禁止运行脚本” | PowerShell 执行策略限制 | 改用 cmd 执行 activate 脚本;或用Set-ExecutionPolicy放行 |
| 同一个包在 A 项目装完,B 项目找不到 | 装到了别的环境/全局环境,没有激活 B 项目环境 | 先conda activate或source venv/bin/activate,再装依赖 |
6.2 排查思路:先分清“环境”与“依赖”的锅
遇到环境相关的报错时,我第一反应永远是先查当前环境状态,再看包版本。如果一上来就查依赖版本,很可能绕弯路。
排查路径我建议这样走:
# 第一步:确认当前身处哪个环境 which python python -c "import sys; print(sys.prefix)" # 第二步:确认关键包装在哪、什么版本 python -m pip show requests # 第三步:确认是否激活了正确的 shell 配置 echo $VIRTUAL_ENV # Linux/macOS echo %VIRTUAL_ENV% # Windows cmd我见过的很多鸡飞狗跳的问题,最后都归结为一句“哦,原来当时没激活环境”。所以这套三板斧命令我建议你贴到笔记里,排障时先用一遍,效率立竿见影。
6.3 激活后命令找不到的小坑
Linux 下偶尔会遇到激活虚拟环境之后,python和pip都正常,但敲一个你刚装的可执行命令(比如gunicorn、scrapy)却提示 command not found。这一般不是环境没配对,而是 shell 的 PATH 更新没生效,或者你用了不同用户/不同 shell 会话。
确认方法:看blog_env/bin/里有没有这个命令文件。
ls blog_env/bin | grep gunicorn如果有,但终端还是找不到,试着重新deactivate再激活一次;如果还不行,用绝对路径执行:
blog_env/bin/gunicorn -c gunicorn.conf.py app:app这种方式虽然丑了点,但在 CI 脚本和 systemd 服务里反而是标配,因为服务进程本身不需要激活环境,直接调用环境里的二进制是最稳的。
6.4 系统 Python 与虚拟环境 Python 混淆问题
另一种在真实开发中很容易踩的坑,是你在虚拟环境里调用了系统 Python 的 pip。比如:
source blog_env/bin/activate python -m pip install flask # 正确:用的是虚拟环境里的 python pip install flask # 有风险:如果 pip 本身来自全局,可能装到全局pip这个可执行文件本质上是一个脚本,它的头部会标出指向哪个解释器。但如果你在激活环境之前已经安装了某个pip,并且 shell 的 PATH 解析顺序偏了,pip install就会跑到全局环境去。所以我的习惯是所有包操作都通过python -m pip来做,这个习惯帮我避开了无数无意义的问题。
7. 从 venv 到 uv:现代工具链带来的体验升级
7.1 uv 到底快在哪里
最近这一年,我明显感觉到 Python 包管理工具链在剧烈变动,其中最显眼的当属 uv。它底层用 Rust 写成,核心号称“比 pip 快 10-100 倍”,实际用下来创建虚拟环境和安装依赖的感觉确实像开了挂。
uv 官方推荐的管理方式是直接在项目根目录操作,不需要先手动创建虚拟环境。你可以把 uv 理解成一个融合了“虚拟环境管理 + 依赖解析 + pip 替代”的综合体。最基础的一条命令:
uv venv new_env激活跟你用 venv 一样,并不特殊。
如果你想省事,uv 还可以帮你自动创建虚拟环境并激活:
uv venv .venv source .venv/bin/activate以后安装依赖都用 uv 替代 pip:
uv pip install flask requests这个命令会把包装进当前激活的虚拟环境,行为跟 pip 一样,但速度是真的快,装一百多个包也就几秒。这体验一旦用过就回不去了。
7.2 新老项目怎么平滑切换
如果你现在还在用传统的 venv + pip,别急着推翻重来。我个人的建议是:
对新项目,直接用 uv 起步,建.venv目录,用pyproject.toml或者requirements.txt管理依赖。对存量项目,可以先用传统流程保持稳定,等到有一个合适的时间窗口,再尝试用uv pip sync requirements.txt来做依赖同步。uv 兼容 requirements.txt,迁移成本比你想象的低得多。
网上那些教程里动不动说“全面切换到 uv”,我建议打个折。你可以在服务器上用 uv 把环境重建一遍,测试一下所有脚本和启动命令是否正常,再决定要不要切。切换工具链本身就是一种工程变更,需要配套测试和回滚方案。
7.3 我对工具链演进的一点体会
从 virtualenv 到 venv,从 pip 到 pip-tools、poetry,再到现在的 uv,Python 的依赖管理工作一直在往前跑。本质原因就是 Python 生态的包数量太庞大,版本兼容问题太复杂,手工作业根本扛不住。作为使用者,我的态度是:不盲目追新,但保持开放。每年抽点时间看看主流工具发生了什么变化,挑一两个新工具在个人项目上试用,比等到社区把新工具全面普及后再被逼着迁移要从容得多。
8. 个人经验补充:一条项目管理习惯
最后想分享一个我在团队里强调了好几年的习惯:虚拟环境和依赖清单,必须跟代码一起走,而不是留在某台开发者本机里。项目仓库里统一包含requirements.txt(或environment.yml、pyproject.toml),同时明确写入 README:环境怎么建、依赖怎么装、怎么跑测试。
不要小看这条习惯。我自己带过的项目里,凡是环境配置不跟随仓库走的,几乎 100% 会出现“新人进来配了三天环境还没跑通”的窘况。而凡是按规范写好环境配置的,新成员基本半小时之内就能基于文档把开发环境拉起来,效率差距肉眼可见。
具体落实时,我通常会在README里写死命令,比如:
python -m venv .venv source .venv/bin/activate pip install -r requirements.txt然后在.gitignore里写死忽略规则:
.venv/ __pycache__/这两步很简单,但它们把“环境可复现”这个原则真正落到了日常流程里。也正因为这些基础动作扎实,后来项目涉及 CI、部署、多机器协同的时候,我们几乎没再遇到过环境层面的阻塞。
虚拟环境这门手艺,本质上不复杂,但它是一块绕不开的基石。学会创建环境、隔离依赖、管理清单、处理迁移,你就能把 Python 项目从“在自己机器上碰巧能跑”升级成“在任何机器上都能稳定跑”,这才是专业开发的底气。