005、虚拟环境完全指南:venv、virtualenv、conda 的原理与选择
一个让我熬夜到凌晨3点的依赖冲突
去年接手一个老项目,Django 1.11 + Python 2.7,跑在CentOS 6上。我本地是Python 3.9,pip install -r requirements.txt 直接炸了——Django版本不兼容,接着numpy报错,然后pandas说找不到某个C扩展。更离谱的是,我同时还在开发一个新项目,需要Python 3.10和最新的FastAPI。两个项目互相打架,pip list一看,全局site-packages里混着十几个版本的包,谁是谁的依赖都分不清。
那天晚上我干了件蠢事:手动删了/usr/lib/python3.9/site-packages里一半的文件夹,然后系统级的yum包管理器罢工了。第二天重装了系统。
这就是虚拟环境要解决的问题。别像我一样用血泪换教训。
venv:Python官方给的“够用”方案
Python 3.3开始内置了venv模块,3.5之后成为推荐方式。它的原理很简单:创建一个独立的目录,里面放一个Python解释器的软链接,外加一个独立的site-packages目录。
python3-mvenv myproject_envsourcemyproject_env/bin/activate# Linux/Macmyproject_env\Scripts\activate# Windows激活后,which python指向的是虚拟环境里的解释器,pip install安装的包全放在myproject_env/lib/python3.x/site-packages/里。退出用deactivate。
原理层面:venv本质上修改了sys.path。激活脚本把虚拟环境的bin目录加到PATH最前面,同时设置VIRTUAL_ENV环境变量。Python启动时会检查这个变量,优先加载虚拟环境里的包。
坑点:
- 不能移动虚拟环境目录。一旦移动,所有软链接路径都失效。我见过同事把项目从
/home/user/project移到/data/project,然后虚拟环境直接报废。 - 跨Python版本不兼容。venv只能基于创建时指定的Python版本,不能切换。你没法用Python 3.9的venv跑Python 3.10的代码。
- 系统级包污染。如果创建时用了
--system-site-packages,虚拟环境会继承全局包。这通常是个坏主意——你本来就是为了隔离,结果又混进去了。
什么时候用venv:单项目、单Python版本、不需要频繁切换环境、团队里所有人都用Python 3.6+。简单场景下venv够用,别过度设计。
virtualenv:venv的“亲爹”和增强版
virtualenv比venv出现得更早,支持Python 2.7到3.x。venv其实是virtualenv的一个子集实现,但virtualenv功能更丰富。
pipinstallvirtualenv virtualenv myenv--python=python3.8# 指定Python版本sourcemyenv/bin/activate核心差异:
- 支持指定任意Python解释器路径。你可以用
--python=/usr/local/bin/python3.10创建一个基于3.10的环境,即使系统默认是3.9。 - 创建速度更快。virtualenv用了缓存机制,第二次创建相同版本的环境时,会复用之前下载的Python二进制文件。
- 跨平台一致性更好。Windows上的路径处理、权限管理比venv成熟。
一个让我抓狂的场景:某次部署到内网服务器,Python版本是3.6.8,但开发机是3.8.5。venv创建的环境直接报Fatal Python error: Py_Initialize: unable to load the file system codec。换成virtualenv指定--python=/usr/bin/python3.6,问题解决。
注意:virtualenv的--relocatable选项号称可以移动环境目录,但实际效果不稳定。我试过三次,两次报错。别依赖这个功能。
conda:数据科学家的瑞士军刀
conda不是Python专属的虚拟环境工具,它管理的是整个软件栈,包括Python解释器、C库、R语言包等。Anaconda发行版自带conda,Miniconda是精简版。
conda create-ntf2python=3.8tensorflow=2.4conda activate tf2原理完全不同:conda不依赖系统Python。它自己维护一个独立的包仓库(channels),包格式是.tar.bz2或.conda,包含二进制文件、依赖元数据。创建环境时,conda会下载指定版本的Python解释器(比如Python 3.8.10的二进制包),然后安装依赖。这意味着你可以在没有root权限的服务器上装任何版本的Python。
优势:
- 解决非Python依赖。比如
numpy依赖BLAS库,opencv依赖libjpeg。pip只能装Python包,conda可以装C库。我处理过一个项目需要libxml2的特定版本,pip装不了,conda一行搞定。 - 环境克隆和导出。
conda env export > environment.yml可以精确记录所有包版本,包括渠道来源。conda env create -f environment.yml在另一台机器上重建完全一致的环境。 - 多版本Python共存。一个conda环境用Python 3.7,另一个用3.10,互不干扰。
代价:
- 体积巨大。一个conda环境动辄几百MB,因为包含了完整的Python解释器和依赖库。venv环境通常几十MB。
- 包安装慢。conda的依赖解析算法(SAT solver)很慢,尤其是包多的时候。
conda install等几分钟是常事。 - 与pip混用有风险。conda环境里用pip装包,conda不知道这些包的存在,可能导致依赖冲突。我见过有人先conda install tensorflow,然后pip install torch,结果两个框架的CUDA版本打架。
最佳实践:conda环境里尽量只用conda装包。如果必须用pip,先装完所有conda包,最后用pip。并且不要频繁在conda和pip之间切换。
三者的选择策略
选venv的场景:
- 项目简单,只有纯Python依赖
- 团队统一使用Python 3.8+
- 部署环境是Docker容器(容器本身已经隔离了)
- 你不想多学一个工具
选virtualenv的场景:
- 需要支持Python 2.7(老项目遗留)
- 需要指定非默认Python版本
- 创建环境频繁,追求速度
- 在Windows上开发,需要更好的兼容性
选conda的场景:
- 数据科学、机器学习项目(numpy、pandas、tensorflow、pytorch)
- 需要管理非Python依赖(C库、R包)
- 没有root权限,需要安装特定Python版本
- 需要精确复现环境(学术研究、生产部署)
我的个人经验:
- 日常开发用venv就够了,别为了炫技上conda
- 数据科学项目必用conda,省去编译C扩展的痛苦
- 生产环境用Docker + venv,Docker负责系统级隔离,venv负责Python包隔离
- 永远不要用系统Python(
/usr/bin/python3)直接跑项目,除非你想重装系统
一个实战案例:从混乱到有序
假设你接手一个项目,requirements.txt里有:
Django==2.2 numpy==1.16.4 pandas==0.24.2 scikit-learn==0.21.3你的系统Python是3.9,但Django 2.2只支持到Python 3.7。怎么办?
错误做法:pip install -r requirements.txt,然后祈祷。结果Django报错,numpy编译失败,pandas说版本不兼容。
正确做法:
- 用conda创建环境:
conda create -n legacy_project python=3.7 - 激活环境:
conda activate legacy_project - 安装依赖:
conda install django=2.2 numpy=1.16.4 pandas=0.24.2 scikit-learn=0.21.3 - 如果某个包conda仓库没有,再用pip:
pip install some-missing-package
这样Python版本、C库依赖、Python包版本全部锁定。项目跑起来后,conda env export > environment.yml,下次直接conda env create -f environment.yml重建。
最后说几句
虚拟环境不是银弹。它解决的是依赖隔离问题,但解决不了代码质量问题。我见过有人一个项目里建了5个虚拟环境,每个环境装不同的包,代码里到处是sys.path.append——这是把问题复杂化了。
一个项目一个环境,环境文件(requirements.txt或environment.yml)纳入版本控制。别把虚拟环境目录提交到git里,那是二进制文件,diff毫无意义。
还有,别在生产服务器上激活虚拟环境跑代码。用Docker打包整个环境,或者用pip的--target参数指定安装路径。激活脚本是给开发用的,不是给生产用的。
最后一条血泪教训:定期清理不用的虚拟环境。我电脑上曾经有47个虚拟环境,占了几十GB空间。conda env list一看,一半是两年前的项目,早就不维护了。conda env remove -n old_project,干净利落。
虚拟环境是工具,不是信仰。选最适合你当前场景的那个,别纠结。