讲个真事儿。
我们组有个实习生,入职第一天我给他布置了个任务:把项目跑起来。项目不复杂,就是一个Flask应用,依赖写在requirements.txt里。
他打开README,照着文档敲了四行命令:
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt
python app.py
第一行就报错了。错误信息是No module named venv。我过去一看,他系统里的Python是3.5版本,venv模块在3.3就有了,不应该没有啊。
再一看,他用的不是系统Python,是Anaconda自带的Python。而Anaconda的Python,默认是没有venv这个模块的。
我帮他装了venv,跑通了项目。第二天,隔壁组的算法同事来找他,说有个模型需要Python 3.8环境(我们项目是3.9),让他帮忙跑一下。他心想,简单,换版本呗。结果venv根本不支持指定Python版本,它只能用创建环境时那个Python解释器。
两个小时后,他的电脑里已经装了四个版本的Python,三个虚拟环境工具,还有一堆乱七八糟的软链接。
那天我意识到一件事:虚拟环境这个东西,看起来是“环境隔离”,但venv和conda这两套工具,背后的逻辑完全不同。 用惯了其中一种的人,切换到另一种时,一定会踩坑。
先搞清楚虚拟环境到底解决什么问题
在没有虚拟环境之前,Python世界是这样的:
你有一个全局的Python环境,所有包都装在一个地方。项目A需要requests==2.28,项目B需要requests==2.0。你装完A再装B,B会把A的依赖覆盖掉。A就挂了。
而且不同项目可能需要不同版本的Python本身。A用3.9,B用3.11。全局只有一个Python版本,怎么切换?
虚拟环境解决的就是这两个问题:
- 包隔离 —— 每个项目有自己独立的
site-packages目录,装什么包互不影响 - Python版本隔离 —— 每个环境可以用不同版本的Python解释器
但问题是,venv和conda对这两个问题的解法,走了两条完全不同的路。
venv:轻量级隔离,Python版本绑定
venv是Python官方自带的虚拟环境工具。它的工作方式很简单:
当你执行python -m venv myenv时,它做了一件事:在myenv目录里复制一份当前Python解释器的副本。然后你激活这个环境时,实际上是调整了PATH环境变量,让命令行优先去myenv/bin里找python和pip。
关键点来了:venv里用的Python,就是你创建环境时系统里那个Python。你在Python 3.9下创建的环境,永远是3.9,无法切换到3.8。想换版本?重新创建一个环境。
# 用Python 3.9创建环境,永远只能跑3.9
python3.9 -m venv myenv
# 想用3.8?换一个解释器重新创建
python3.8 -m venv myenv38
所以venv的隔离,是包级别的隔离,不是Python版本级别的隔离。它像一层薄薄的壳,只负责把当前项目的包和系统全局的包隔开。
因为轻量,venv用起来很直接:
# 创建
python -m venv .venv
# 激活(Linux/Mac)
source .venv/bin/activate
# 激活(Windows)
.venv\Scripts\activate
# 安装包
pip install requests
# 退出
deactivate
优点:轻、快、简单、随Python自带,不需要额外安装。
缺点:不能管理多个Python版本;不同项目需要手动管理不同的环境目录;切换环境靠手动激活/退出,没有中心化的管理界面。
conda:重量级全能选手,环境完全独立
conda走的是另一条路。它不只是一个Python虚拟环境工具,它是一个通用包管理器和环境管理器,可以管理Python、R、Ruby、Lua等多种语言的包。
conda的环境是完全独立的。一个conda环境可以指定使用特定版本的Python,而且这个Python是conda自己下载和管理的,不依赖系统里装的Python。
# 创建一个Python 3.8的环境,名字叫py38
conda create -n py38 python=3.8
# 切换到py38环境
conda activate py38
# 创建一个Python 3.9的环境
conda create -n py39 python=3.9
# 切换
conda activate py39
看到了吗?conda直接解决了Python版本切换的问题。每一个环境都是一个独立的小宇宙,里面有独立的Python解释器、独立的包、独立的目录结构。
conda的包也不只是Python包。你需要装一个C++编译器?conda install gcc。你需要装FFmpeg?conda install ffmpeg。这些都是conda管理的,不依赖系统包管理器。
优点:支持多版本Python;环境管理有中心化命令(conda env list);不只是Python包,还能管理系统级依赖;特别适合数据科学场景。
缺点:重。一个miniconda安装包就好几百MB,完全版Anaconda有几个GB。包安装速度有时比pip慢。某些包在conda源里没有,还得用pip去装。
核心区别:一个是“容器”,一个是“仓库”
我用一个比喻来帮你理解两者的本质区别:
venv像一个个独立的“容器”。每个容器里装的是一套特定版本的包,但容器本身是在一个固定的Python解释器上搭建的。你有很多个容器,它们各自装着不同的包,但底层用的Python版本都一样(或者你手动用不同版本的Python创建容器)。
conda像一个大“仓库”。仓库里有很多个货架,每个货架放着一套完整的环境——这个货架放着Python 3.8和它的包,那个货架放着Python 3.9和它的包。你从仓库里把一个货架整搬出来用,用完放回去,再搬另一个。每个货架都是完备的,自包含的,之间完全不干扰。
所以这两个工具在以下维度上完全不同:
| 维度 | venv | conda |
| 管理Python版本 | 否,只能使用创建时的解释器 | 是,每个环境可以指定版本 |
| 包来源 | PyPI(通过pip) | conda渠道 + PyPI(通过pip) |
| 环境存储位置 | 项目目录内(通常) | 统一目录(~/miniconda3/envs/) |
| 切换方式 | 激活/退出脚本 | conda activate/deactivate |
| 跨语言包管理 | 否 | 是 |
| 安装包大小 | 轻量(几十KB到几MB) | 重量级(上百MB到GB) |
| 适用场景 | Web开发、通用Python项目 | 数据科学、机器学习、多语言项目 |
混用的坑:当venv遇到conda,或者反过来
你可能看过这样的文章:“conda和venv可以一起用,conda管环境,venv管包。”理论上可以,但实践中全是坑。
坑一:在conda环境里用venv
如果你在conda基础环境里创建venv,venv会复制conda的Python解释器。这个Python解释器的路径指向conda的目录,它的sys.path包含了conda的site-packages。结果就是,虽然你用了venv,但有些包还是会从conda里加载,造成莫名其妙的冲突。
坑二:在venv里用conda install
conda install不认识venv的环境,它只管把自己的包装到conda管理的目录里。你在venv激活状态下执行conda install,装是装上了,但venv根本找不到它。
坑三:混用pip和conda
这几乎是所有新手conda用户都会遇到的问题。conda环境里用pip装包,装了之后conda不知道,环境状态就不一致了。下次你用conda安装别的包时,conda可能会“自作主张”把pip装的包覆盖掉。反过来,pip也不知道conda装了什么,可能在安装新包时覆盖conda依赖的包,直接把环境干碎。
我见过最离谱的一次:一个同事在conda环境里用pip install了TensorFlow,然后conda install了一个依赖numpy的包,conda发现numpy版本不对,把numpy降级了。TensorFlow依赖高版本numpy,直接炸了。整个环境废掉,重新建了一个。
那到底该用哪个?
这个问题没有标准答案,但有几条判断标准:
强烈建议用venv的情况:
- 你是纯Python Web开发(Flask/Django/FastAPI)
- 项目不依赖C++库、CUDA、FFmpeg等系统级依赖
- 团队里所有人都在用Linux/Mac,且系统Python版本统一
- 你希望依赖文件用
requirements.txt管理,配合Poetry或pip-tools
强烈建议用conda的情况:
- 你在做数据科学/机器学习,依赖numpy、pandas、scikit-learn、TensorFlow、PyTorch等
- 你需要CUDA、cuDNN等GPU支持
- 你需要在Windows上开发(conda在Windows上体验远好于venv+系统Python)
- 你需要不同项目用不同Python版本,而且切换频繁
- 你需要管理非Python的依赖(例如R语言包、C编译器、FFmpeg)
如果你非要用conda,又非要用pip,记住一条铁律:
- 先用conda装你能装的所有东西
- 再用pip装conda里没有的东西
- 装完后,尽量不要再混用
- 每次安装后跑一下
conda list确认环境状态
一个更现代的选择:只用poetry
如果你不想在venv和conda之间纠结,还有一个更干净的选择:只用Poetry。
Poetry内置了虚拟环境管理(类似venv),同时解决了依赖解析(类似poetry.lock)和打包发布的问题。它不会帮你管理Python版本,但它会帮你管理项目的虚拟环境位置,以及所有依赖的版本锁定。
配合pyenv(专门管理Python版本的工具)一起用,可以组合出非常干净的工作流:
pyenv install 3.11.0
pyenv local 3.11.0
poetry init
poetry add requests
poetry shell
这个组合的优点是每个工具只做一件事:
- pyenv管Python版本
- Poetry管依赖和虚拟环境
- 不打架,不越界
总结
venv和conda没有谁更高级、谁更low,它们服务的对象不同。
- venv是一个专注的工匠——只做包隔离这一件事,做得轻快简单。
- conda是一个全能的管家——不仅管包隔离,还管Python版本、管系统依赖、管跨语言包,什么都管,但重。
选哪个,取决于你是什么类型的开发者,做什么类型的项目。
最后说一个实际建议:同一个项目里,只用一套虚拟环境方案。 要么全用venv(配合pip/poetry),要么全用conda。不要两个混着用,除非你愿意花时间弄清楚两种机制各自的边界在哪里。
而我那个实习生,后来我让他重装了系统。这次只装了Python官方版本加Poetry,再也没碰过conda。