刚接手公司那台旧服务器的时候,我差点被一团乱麻的Python环境搞崩溃。队里几个人各写各的脚本,有人用Django 2.2,有人项目里非要Django 4,还有一个量化小组在跑pandas和numpy的老版本组合。当时谁要装个新包,直接在全局pip install,结果今天升级了某个依赖,明天另一个脚本就报ImportError。后来我花了整整一个周末把所有项目按虚拟环境拆开,从此再没为“环境打架”的事情半夜起来修过。
这篇内容不是照搬官方文档,而是结合我自己的使用习惯,把Python虚拟环境从原理到创建、从编辑器接入到项目迁移、再到绕坑经验一次性讲清楚。不管你是刚入门的小白,还是被依赖问题折磨过的老开发,按着下面的节奏走,基本能把“创建虚拟环境”这件事彻底拿捏住。
1. 为什么每个 Python 项目都应该有自己的独立“运行房间”
很多人第一次接触虚拟环境,最直接的疑问是:我明明可以用pip把包装到系统里,为什么非要搞出一个“隔离空间”?回答这个问题,最好的方式不是讲定义,而是讲一次我真实遇到过的依赖事故。
1.1 一次依赖冲突事故:我的 Django 教训
去年我要维护一个老项目,Django版本被锁在2.2,因为里面有个第三方管理后台插件只兼容这个区间。后来同事新开了个接口服务,需要Django 4.1的新特性。如果两套代码共用同一个全局site-packages,pip在安装Django 4.1的时候会直接卸载掉2.2,老项目的urls.py马上开始报错。哪怕你用pip install django==2.2强行装回去,新项目又启动不起来了。你想在两个版本之间来回横跳,结果就是无限循环。
这种问题在依赖稍微多一点的现实项目里几乎是必然发生的。不是只有Django才会这样,TensorFlow和PyTorch、requests和httpx、Pillow和opencv,只要版本敏感就会出现相似的情况。虚拟环境的价值在于:每个项目拥有独立目录,互相看不见,切换项目就切换环境,彻底把“全局公共区”的共享问题变成“项目自治”。
1.2 虚拟环境的本质:目录隔离与解释器重定向
从技术底子上看,Python虚拟环境做的事情并不复杂。你在某个目录下执行python -m venv .venv后,这个目录里会出现bin(Windows下是Scripts)、lib、include等结构。核心思路是创建一个“局部Python运行副本”,里面有一份指向基础解释器的链接,以及一个空的site-packages目录。当你激活这个环境之后,环境变量PATH会被调整,让.venv/bin排在最前面,那么你在命令行里敲python或者pip,实际拿到的就是虚拟环境里的解释器,而不是系统全局的。
这里有个容易误解的地方:虚拟环境并不重新编译Python解释器,它是对基础解释器的引用。但site-packages目录是全新的,所以包装了哪些、装了什么版本,各个环境互不干扰。简单类比一下:全局环境就像一个大办公室,大家的工位和抽屉混在一起,谁动了公共区域都会影响所有人;虚拟环境则是给每个项目单独开了一间带锁的工位,你在自己桌子上摆什么都不会打扰别人,离开时锁上门就行。
1.3 虚拟环境能解决的问题:清理、迁移、多人协作
我整理过虚拟环境在日常开发里的几个核心收益,选型时可以直接对照:
- 依赖隔离:每个项目锁定自己的包版本,不会互相覆盖,也不会影响系统自带的Python。
- 清理方便:环境就是一个文件夹,不要了直接删掉,不污染系统。比起全局pip装乱了再逐个卸载,省心太多。
- 迁移可复现:通过
pip freeze导出依赖清单,到新机器上执行一次安装,几十秒钟就能还原出一样的运行环境。 - 权限安全:在公司机器上如果没有root权限,全局安装经常碰壁,虚拟环境安装在用户目录,完全绕开权限限制。
- 多人协作:团队提交
requirements.txt,每个人用虚拟环境拉到同一组依赖版本,减少了“在我电脑上明明能跑”的争议。
2. 工具选型:venv、conda 与 uv 该怎么选
创建虚拟环境不是只有一条路。早期用virtualenv,后来Python官方把venv装进了标准库,做科学计算的人习惯用conda,最近又冒出个速度惊人的uv。这几个工具各有自己的适用场景,选错了不是不能干活,而是会在后续维护时多绕弯子。
2.1 venv:不装额外依赖的第一选择
venv是Python 3.3以后自带的标准库模块,它的优势可以用一句话总结:只要你的电脑能跑Python,就能直接创建环境,不需要再安装任何东西。对于大多数Web项目、脚本工具、一般的自动化任务,venv完全够用。它的缺点是隔离的是Python包,如果你需要同时管理多个Python版本,比如一个项目要3.8、另一个要3.11,单靠venv不够灵活,因为它默认只能基于当前的解释器创建环境。
我的习惯是:只要没有特殊条件,优先使用venv。原因很简单,少依赖一个工具就少一层维护成本,而且它在Windows、macOS、Linux上的行为已经打磨得很稳定。
2.2 conda/anaconda:应对科学计算与多版本 Python
conda跟venv不是一个层面的东西。它本身是Anaconda发行版里自带的包管理器,不仅能创建Python虚拟环境,还能管理Python解释器版本,甚至能安装非Python的二进制库,比如CUDA相关的驱动依赖,还有不少C扩展库。conda create -n py311 python=3.11这种命令可以直接拉一个新版本的Python解释器出来,这是venv做不到的。
如果你搞数据分析、机器学习,或者需要在同一台机器上给不同项目提供不同Python版本,conda会是更省心的选择。代价是Anaconda整体比较庞大,安装包动辄几GB,而且conda的依赖解析速度明显比pip慢。现在很多人会用miniconda而不是anaconda来减负,只保留conda核心,装包再按需进行。
2.3 uv:用“快”解决虚拟环境体验问题的后起之秀
uv是最近两年热度很高的Python包管理工具,用Rust写的,核心卖点就是“快”。我第一次跑uv venv的时候,体感几乎是瞬间完成,比原本python -m venv还要轻快不少。它不光能创建虚拟环境,还能替代pip安装依赖,甚至能解析版本依赖关系,把整个工作流统一起来。
对于新项目,我现在的倾向是直接上uv。尤其配合uv sync这类命令,根据pyproject.toml一键安装所有依赖并锁定版本,体验非常接近现代语言的标准包管理器。不过要留意一点,uv工作流比较新,如果你的团队还在用老一套的requirements.txt,直接切换可能带来学习成本,这时候先用venv过渡也是一种理性选择。
下表是我在项目选型时常用的对比方向,直接判断即可:
| 对比维度 | venv | conda | uv |
|---|---|---|---|
| 安装成本 | Python自带 | 需装Anaconda/miniconda | 单文件安装 |
| 是否支持多Python版本 | 不支持 | 支持 | 支持 |
| 依赖安装工具 | pip | conda + pip | uv pip管理 |
| 适用场景 | 通用项目 | 科学计算/数据分析 | 新项目/追求效率 |
| 创建环境速度 | 中 | 慢 | 快 |
| 社区成熟度 | 非常高 | 高 | 快速增长中 |
3. 创建虚拟环境实操:三种方式我都跑了一遍
理论说再多,不如直接把命令跑一遍。这个部分我按venv、conda、uv三种方式分别给出可复制的步骤,并标注了Windows与Linux/macOS的差别。如果你只想要一个答案,就直接跳到3.1,先解决当前问题,后面再探索其他工具。
3.1 venv 创建与激活的完整命令
第一步,先确认Python版本。打开终端,执行:
python --version我建议Python版本在3.8以上再用venv,太低的话标准库的venv模块在某些场景下会有兼容问题。确认没问题后,进入你的项目目录,创建虚拟环境:
python -m venv .venv.venv是虚拟环境目录的名字,你完全可以叫venv或者env,不过业界习惯用.venv,因为点开头的目录在文件管理器里默认隐藏,视觉上不会让项目根目录显得太乱,而且在VSCode等编辑器里也更容易被自动识别。
创建之后,下一步是激活环境。在类Unix系统(Linux/macOS)上:
source .venv/bin/activate在Windows的cmd里:
.venv\Scripts\activate.bat在Windows的PowerShell里:
.venv\Scripts\Activate.ps1如果你用PowerShell执行激活脚本时报错,说“在此系统上禁止运行脚本”,那是因为Windows默认的执行策略限制。可以用管理员权限打开PowerShell执行一次:
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser设置完之后,重新打开终端再激活即可。
激活成功的标志是命令行的提示符前面多了(.venv),像这样:
(.venv) user@host:~$此时你再运行下面这条命令:
which python看到的路径应该是项目目录下.venv/bin/python,这就说明你已经在虚拟环境里了。然后就可以安心用pip安装依赖:
pip install requests pip install django==4.1.7需要退出环境时,执行:
deactivate3.2 conda 创建虚拟环境以及常见参数
如果你用的是conda,第一步不是创建环境,而是先想好要给这个环境指定什么Python版本。这样做最大的好处是不同项目可以完全独立使用不同的解释器,不用为了某个老项目把全局Python改成旧版。
我常用的创建命令是:
conda create -n alice python=3.10 -y这里的-n alice是给环境命名为alice,你可以换成项目名,比如spider、django-web都行。python=3.10是要求conda解析出一个3.10版本的Python,-y表示遇到确认提示自动选yes,免得手动回车。
创建好后激活:
conda activate aliceWindows下如果在Anaconda Prompt里操作,命令一样。激活之后,命令行提示符会带上(alice)。这时候你再执行python -V,可以看到当前环境指向的是刚创建的Python版本。
conda还有一个很好用的能力:环境列表查看。想知道自己创建过哪些环境,直接运行:
conda env list输出里会同时显示环境名和对应的目录路径。
如果你希望创建环境的同时把常用科学计算包也装上,可以一步到位:
conda create -n ml python=3.10 numpy pandas jupyter -y这样环境创建完,numpy、pandas和jupyter都已经可用了,省得后面再逐个装。缺点是conda解析这种综合命令时会比较慢,可能要一两分钟,属于正常现象。
3.3 uv 快速创建虚拟环境与切换
uv的用法对熟悉现代包管理器的人来说非常友好。先安装uv,macOS或Linux可以用系统的包管理器,Windows上我一般用pip:
pip install uv或者你也可以直接下载官方提供的可执行文件。安装完成后,创建虚拟环境:
uv venv .venv --python 3.11--python参数可以省略,省略时会用当前默认的Python版本。创建的速度非常快,几乎不会感受到等待。
激活方式跟venv基本一样。激活后,你可以直接用uv pip替代pip来安装依赖:
uv pip install django我特别想提一下uv的“环境切换”体验。当你维护多个项目、每个项目都有独立环境时,传统方式需要挨个激活、退出,容易搞混。uv的思路是结合pyproject.toml,每个项目根部放一个配置文件,进入目录后执行:
uv sync它会自动根据配置创建好环境、装好依赖,你不用关心环境具体叫什么名字。这种“进入目录即环境就绪”的方式,省掉了大量心智负担,也是越来越多人喜欢在切换项目时用uv的原因。
4. 把虚拟环境接进 VSCode 与 Jupyter,避免“解释器不对”的困扰
很多人在命令行里能把虚拟环境玩明白,一回到编辑器就迷糊了。最常见的问题是:明明已经激活了环境,VSCode的终端里也显示着(.venv),可点击运行脚本时用的还是全局Python,或者是环境中根本没有的那个包。这背后十有八九是“解释器没有正确选择”。
4.1 VSCode 选择解释器与终端激活的联动
在VSCode里打开项目文件夹后,按下Ctrl+Shift+P,输入“Python: Select Interpreter”,回车。这时会弹出一个列表,里面包含VSCode扫描到的所有Python解释器。你要选择带有.venv标记的那个路径,通常长这样:
./.venv/bin/python选中之后,VSCode右下角的状态栏会显示当前解释器路径。这里会出现一个非常有意思的现象:即使你不在终端里手动激活环境,VSCode运行Python脚本时也会使用这个解释器,因为编辑器是通过解释器路径直接调用Python,而不是依赖终端的PATH。
但如果你打开VSCode的内置终端,还希望终端里也激活环境,有一种做法是依赖VSCode的“Python: Select Interpreter”联动机制:当你选择了.venv/bin/python之后,VSCode会自动在新建终端时激活对应的环境。实测下来这个功能默认是开启的,个别版本可能会失效。失效时你可以在.vscode/settings.json里加一条:
{ "python.terminal.activateEnvironment": true }如果你发现终端里没有自动激活,也不要在意太多,只要解释器选对了,脚本运行就没有问题。开发时在命令行少敲几次activate,会舒服很多。
4.2 Jupyter notebook 使用虚拟环境内核
Jupyter notebook的虚拟环境配置,坑比VSCode更多。因为notebook内核默认跟Python环境绑定,你在notebook里执行代码时,用的解释器取决于内核选择的路径,而不是当前终端激活的环境。
正确做法是:先激活虚拟环境,然后在环境内安装ipykernel:
source .venv/bin/activate pip install ipykernel然后把当前环境注册成Jupyter的一个内核:
python -m ipykernel install --user --name myenv --display-name "Python (myenv)"这行命令有两个参数需要注意:--name是内核的内部标识,必须唯一;--display-name是Jupyter界面下拉列表里显示的名字,你可以写成容易识别的名称。注册完成后,启动Jupyter:
jupyter notebook新建notebook时,在“Python”下拉菜单里选中刚才添加的“Python (myenv)”,执行代码时使用的就是虚拟环境里的解释器和依赖了。
如果你启动notebook后发现下拉菜单里没有你的环境,很大概率是ipykernel没装进虚拟环境,或者注册时用了root权限导致不一致。重新走一遍上面的步骤,同时在安装时不要带--user(除非遇到权限报错),基本能解决。
5. 虚拟环境迁移:换电脑、换服务器时的实操流程
虚拟环境本身不应该被直接拷贝着用。很多新人会想着:“我直接把.venv整个压缩包发到新机器上不就行了吗?”实际操作后就会遇到各种奇怪问题——新机器上包路径不对、二进制库不兼容、依赖丢失。正确做法是生成依赖清单,在新环境里重建。
5.1 requirements.txt 与 freeze 快照
pip freeze是Python生态环境里最基础也是最重要的一项技能。在虚拟环境激活状态下执行:
pip freeze > requirements.txt这个命令把所有已安装的包和精确版本号写进requirements.txt,比如Django==4.1.7、requests==2.31.0。到了新机器上,创建虚拟环境后执行:
pip install -r requirements.txt依赖就会按清单逐个安装。
不过这里我要给一个更稳妥的建议:用pip freeze导出时,里面往往会混入一些传递依赖,就是那些你并没有直接安装、只是因为其他包才被带进来的库。环境多了之后,这类直接依赖和间接依赖容易混在一起。我更推荐在项目里单独维护一个requirements.in文件,记录直接依赖,然后通过pip-compile工具生成requirements.txt。但对一般项目来说,先用freeze把环境跑起来,完全没有问题。
5.2 conda 环境导出和跨平台注意事项
conda环境迁移有自己的一套命令。在源机器上执行:
conda env export > environment.yml这个YAML文件会记录环境内的所有conda包、pip依赖以及下载渠道。新机器上恢复:
conda env create -f environment.yml如果只是跨Linux服务器的迁移,这种方式的成功率很高。但如果你在Windows上创建conda环境,然后试图在Linux上恢复,会碰到很多平台相关的二进制包问题。跨系统迁移的时候,最稳妥的方案是只迁移requirements.txt,重新用conda建一个新的干净环境,再安装依赖。
5.3 复制环境时的绝对路径坑
有一类问题经常发生在“直接拷贝.venv”模式里:activate脚本内部有一个VIRTUAL_ENV变量,记录的是环境创建时的绝对路径。当你把整个目录移动到别的位置后,激活脚本会找不到原来的路径,甚至提示No such file or directory。
如果实在要移动,最简单的方法是删除旧的虚拟环境,在新位置重新创建,然后执行pip install -r requirements.txt。对生产环境来说,一定要把环境当成“可重建”资源,而不是“可搬运”资源。数据可以备份,代码可以备份,环境永远用清单来复现。
6. 我遇到过的一些虚拟环境问题与排查记录
虚拟环境本身不难,但真跑到团队环境或者生产服务器上,还是会遇到各种意料之外的问题。我把自己踩过的一些坑整理出来,按照“现象—原因—解决办法”的方式写,你遇到类似问题时可以按图索骥。
6.1 anaconda 创建虚拟环境失败
有段时间同事在Windows上用Anaconda执行conda create -n test python=3.9,屡次报错,提示信息里有“CondaHTTPError”或者“ResolvePackageNotFound”。排查下来发现两个原因:一是conda默认的官方源在主网络下访问很慢,超时导致解析失败;二是conda的缓存目录里有一个损坏的锁文件。
解决办法分两步走。先清理缓存和锁:
conda clean --all -y然后配置一个镜像源。这里不展开细节,你只需要在用户目录的.condarc文件里写上镜像地址,再重试创建命令即可。如果重试还是失败,可以换个环境名和Python版本试试,排除是特定包版本的问题。
6.2 已激活但是 pip 仍然装进全局
有一个比想象中更常见的场景:终端里明明显示(.venv),但执行pip install之后,pip show显示的路径还是全局site-packages。原因通常是:虚拟环境里没有安装独立的pip模块,导致pip命令被系统全局的pip接管了。
新版venv默认会自带pip,但如果你创建时加了--without-pip参数,或者系统PATH中有别名/软链接抢占了优先级,就容易出现这个问题。处理方法是显式指定模块执行:
python -m pip install --upgrade pip使用python -m pip而不是直接敲pip,能确保走的是当前Python解释器对应的pip。这个习惯我在任何环境里都坚持,能少很多莫名其妙的问题。
6.3 依赖下载慢或失败的一些临时处理
项目需要的第三方包太大,或者源响应慢,只要不是涉及违规网络的行为,都可以用正常的镜像源处理。比如临时指定一个镜像源安装:
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple requests如果想长期生效,可以在虚拟环境的pip.conf(Linux/macOS)或者pip.ini(Windows)里配置index-url。配置完记得重启终端再检查。还有一种情况是某个包编译需要系统底层库,比如pip install pandas在部分精简Linux系统上会因为没有gcc失败。这时候手动安装系统的编译工具后再重试,不是虚拟环境本身的问题。
6.4 删除与重建环境,别让环境越用越脏
环境用久了,pip进进出出,依赖关系会变得混乱。我建议定期做一次“清零重建”:`
deactivate rm -rf .venv python -m venv .venv source .venv/bin/activate pip install -r requirements.txt整个过程不过几分钟,但能解决大部分由依赖残留导致的不确定问题。尤其是当你觉得环境“怎么这么慢”、“偶尔报些奇怪的错误”时,重建比手工排查更高效率。
回到文章开头那个混乱的服务器:现在每个项目都有自己的.venv目录,新同事加入时只需要拉代码、创建环境、安装依赖,整个过程不再依赖某个人“手动维护全局包”。工具可以选venv的轻量,也可以选conda的完整,还可以选uv的极速,但底层逻辑都是一样的:把Python项目从全局混乱中解放出来,让依赖管理从“靠记忆”变成“靠清单”。对我而言,这可能是Python工程化里最值得先做好的一件事。