ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Python多版本共存与虚拟环境管理:pyenv、virtualenv、venv与conda实战指南

Python多版本共存与虚拟环境管理:pyenv、virtualenv、venv与conda实战指南 1. 为什么要折腾Python多版本共存Python 2和Python 3之间的割裂是每个Python开发者都绕不开的一段历史。2020年1月1日Python 2正式停止维护EOL但现实情况是大量线上遗留项目、内部系统、甚至某些特定硬件厂商提供的SDK仍然跑在Python 2.x上。这导致今天我们打开终端时经常要面对这样一个尴尬局面系统自带Python 3但项目里需要Python 2来跑旧脚本或者某些老库比如旧版的MySQLdb、pymongo、paramiko在Python 3下编译不过只能用Python 2环境。更折磨人的是不同项目之间的依赖冲突。你可能有一个用Django 1.8写的旧项目只能在Python 2.7下运行另一个项目用了最新的FastAPI需要Python 3.10以上的特性。如果这些项目都跑在同一台机器上又没有做版本隔离那环境崩溃只是时间问题——最常见的场景就是“我升级了一个包的版本另一个项目直接跑不起来了”。我之前在维护一台老服务器时就吃过这种亏。系统默认Python是2.7为了跑一个数据分析脚本装了numpy结果把系统里某个依赖旧版numpy的服务搞挂了。从那以后我彻底养成了“一个项目一套Python环境”的习惯这也是这篇博文想分享的核心思路把多个Python版本和多个虚拟环境管理清楚各干各的互不干扰。这篇内容不是那种纯概念科普而是面向实际动手操作的人。无论你是在Windows、macOS还是Linux上开发无论你是普通开发者、运维、还是数据分析师只要你需要在同一台机器上同时使用Python 2和Python 3或者多个Python 3小版本这篇文章都适用。读完你就能知道多版本共存的核心原理是什么主流的工具怎么选以及实际操作中有哪些坑必须避开。2. 多版本共存的方案选型与核心原理2.1 直接改系统Python的惨痛教训很多人刚接触多版本需求时第一反应是下载一个Python 2的安装包直接覆盖安装或者修改/usr/bin/python的软链接。这个做法在个人电脑上试运气可能一时能用但隐患极大。以Linux为例系统的很多底层工具比如yum、apt、gnome-terminal依赖系统自带的Python版本。我在CentOS 7上试过把/usr/bin/python从2.7换成3.x的软链接结果yum直接报错因为yum的脚本是用Python 2写的很多语法在Python 3下不兼容。Windows上直接安装多个Python版本也不省心因为安装器会把python.exe写进PATH两个版本很容易互相覆盖最后你在命令行里输入的python到底是哪一个完全取决于安装顺序和注册表里的项。这种“一锅炖”的思路本质上是错误的。多版本共存的前提是每个版本都独立存在互不干扰调用哪个版本要通过显式的方式来控制而不是靠运气。2.2 主流的两种隔离思路解决Python多版本冲突目前主流的方案可以划分为两类它们的隔离维度和适用场景不同。第一类是解释器级别的隔离。典型工具是pyenv。它会把Python 2.7、3.6、3.8、3.10等多个版本分别编译安装到一个统一目录下默认是~/.pyenv/versions/然后通过“shim垫片”机制拦截你在终端里输入的python命令再根据当前目录下的.python-version文件或环境变量把命令“转发”给对应的真实Python解释器。这种方式解决的是“机器上同时有多个Python解释器”的问题。第二类是项目依赖级别的隔离。典型工具是venv、virtualenv和conda。它们创建一个独立的目录里面包含一套独立的Python解释器和site-packages项目A装什么包都不会影响到项目B。这种方式解决的是“即使同一个Python版本不同项目的第三方依赖也不会互相污染”的问题。一个好的多版本共存方案会把这两层结合起来用pyenv管理Python解释器版本用virtualenv或venv管理项目依赖环境。我的建议很简单——解释器用pyenv管环境用venv/virtualenv管两者配合使用。这个组合在Mac和Linux上最顺手也是社区里用的最多的做法。2.3 工具选型对比pyenv、virtualenv、conda为了不让大家纠结我直接整理了一个选型对比表这是我在实际项目中反复比较后的结论工具隔离维度适用场景注意事项pyenv解释器版本需要在多个Python 2/3大版本间切换不解决依赖冲突需要配合虚拟环境virtualenv项目依赖项目级依赖隔离支持任意Python版本Python 2时代的主力工具现在仍需使用Python 2不支持venvvenv项目依赖Python 3.3内置模块轻量简单Python 2不能用旧项目需要用virtualenv替代conda解释器依赖系统库科学计算、数据领域需要管理非Python库管理起来最重环境迁移方便但容易和系统Python产生歧义从这张表里能看出来没有哪个工具是“万金油”。比如你用conda创建一个Python 2.7的环境它把解释器、numpy、scipy、甚至底层的libgcc都管起来的跨平台迁移非常方便。但代价是conda的安装体积大、启动慢、而且它的环境变量激活机制有时会和系统里其他Python工具链冲突。如果你只是想在开发机上同时留着Python 2和Python 3日常用我的首选组合是pyenv virtualenv。如果你主力做数据分析、机器学习conda更省心。这篇文章后面的实操部分我会以pyenv virtualenv这条主线讲透因为它的灵活性和对Python 2的兼容性是目前最稳的。3. pyenv安装Python多版本的核心实操3.1 安装pyenv前的系统依赖准备在装pyenv之前必须先处理好系统级的编译依赖。因为pyenv默认是从源码编译Python不是下载预编译好的二进制包。如果缺少依赖编译过程中会报各种奇奇怪怪的错误非常劝退。在Ubuntu/Debian系统上我用的是这一套命令sudo apt update sudo apt install -y make build-essential libssl-dev zlib1g-dev \ libbz2-dev libreadline-dev libsqlite3-dev wget curl llvm \ libncursesw5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev \ libffi-dev liblzma-dev在CentOS/RHEL系统上对应的是sudo yum install -y gcc gcc-c make patch zlib-devel bzip2-devel \ readline-devel sqlite-devel openssl-devel tk-devel \ libffi-devel xz-devel为什么要装这些简单说Python的很多标准库模块在编译时依赖外部的系统库。比如ssl模块需要libssl-devsqlite3模块需要libsqlite3-devreadline模块需要libreadline-dev。缺少libffi的话用ctypes调用C函数会出问题——装完这些再去编译Python基本一次过。在macOS上我建议先装Homebrew然后执行brew install openssl readline sqlite3 xz zlib tcl-tk注意macOS在较新的Catalina及之后系统版本里默认shell是zsh后面配置环境变量时需要改~/.zshrc而不是~/.bash_profile。这个细节会让很多人卡几分钟提前留意能省事不少。3.2 安装pyenv并配置环境变量安装pyenv本身有两种方式。我推荐直接用官方安装脚本省事出错概率低。以macOS和Linux通用curl -L https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-installer | bash安装脚本执行完后终端会输出几行提示让你把pyenv的初始化配置加到shell的配置文件中。以bash为例需要写入~/.bashrc在macOS上写入~/.zshrcexport PATH$HOME/.pyenv/bin:$PATH eval $(pyenv init -) eval $(pyenv virtualenv-init -)配置完成后执行source ~/.bashrc让配置生效。然后验证pyenv --version如果能看到版本号说明pyenv本体装好了。有几个补充性的经验分享pyenv的下载源是GitHub在国内网络环境较差的时候安装速度极慢甚至失败。如果你遇到下载慢的问题可以考虑用镜像加速比如设置GIT_CURL_VERBOSE1看调试信息定位问题。另外一个常见问题是pyenv init -和pyenv virtualenv-init -两条命令都要写很多人只写了pyenv init -就以为万事大吉结果后面pyenv virtualenv命令根本找不到。3.3 快速编译安装Python 2和Python 3接下来就是核心操作安装多个Python版本。我先装一个Python 2.7.18这是Python 2的最后一个正式版本再装一个Python 3.8.18一个稳定且兼容性极好的Python 3版本pyenv install 2.7.18 pyenv install 3.8.18这个命令会下载Python源码、解压、配置、编译、安装整个过程在性能较好的机器上大概需要3到10分钟。安装过程中如果报错优先去检查上一节说的那些编译依赖是否装齐。有个小技巧如果你需要给Python 2的编译过程加额外的configure参数比如指定sqlite目录可以这样操作CONFIGURE_OPTS--enable-shared pyenv install 2.7.18装完之后用下面的命令查看当前pyenv管理的所有版本pyenv versions输出会显示类似这样的信息* system (set by /home/user/.pyenv/version) 2.7.18 3.8.18注意这里的system指的是系统自带的Python版本例如/usr/bin/python3。如果你不想让系统版本在这些Python版本中“插队”也可以不管它只用自己安装的版本。3.4 理解pyenv的版本选择顺序pyenv在决定你敲python命令时实际使用哪个版本是通过一套优先级规则来确定的。这个规则如果你不理解后面调试环境会很抓狂。优先级从高到低是PYENV_VERSION环境变量最高优先级临时指定用这个当前目录下的.python-version文件项目级指定当前目录往上级目录里的.python-version文件全局配置~/.pyenv/version默认兜底我们最常用的是前三种。比如在/home/user/my_project目录里写入.python-version文件内容为2.7.18那在这个目录下运行python就会自动使用Python 2.7.18。用pyenv local命令可以很方便地写这个文件# 进入项目目录后执行 pyenv local 2.7.18 # 查看当前目录生效的Python版本 python --version这个机制是我非常喜欢的设计它把“版本”和“项目目录”绑定在一起不需要手动激活环境一进入目录就自动切到对应版本。这在部署和切换多个项目时特别好用。但需要注意一点.python-version文件如果被误提交到Git仓库团队成员克隆代码后会自动切到指定版本如果别人机器上没有这个版本就会报错。所以我的习惯是在.gitignore里加上.python-version除非团队有统一的版本管理规范。4. 用virtualenv和venv做依赖隔离4.1 为什么虚拟环境是必需品只有pyenv只能解决解释器版本切换问题还不能真正避免依赖冲突。你再想想这个场景项目A用到requests2.20.0项目B用到requests2.31.0。如果都放在同一个Python环境里pip在装其中一个版本的时候会把另一个覆盖掉。这不算罕见问题而是每天都在发生的。虚拟环境就是解决这个问题的。它本质上是在项目目录下或者独立的目录中创建一个“独立的小型Python世界”有自己的bin/python、bin/pip和lib/pythonX.Y/site-packages。你在里面装什么包都不会跑到全局环境里去。4.2 Python 2项目必须用virtualenv这里有一个关键点Python 2.7的老项目不能直接用venv创建虚拟环境因为venv是Python 3.3才开始引入的模块。Python 2环境只能通过virtualenv来创建。虽然在Python 3环境里也可以给Python 2创建环境virtualenv -p python2.7 myenv但最保险的方式是用Python 2.7自己的pip装一个匹配的virtualenv# 先切换到Python 2.7环境 pyenv shell 2.7.18 # 在Python 2.7下安装virtualenv pip install virtualenv # 创建虚拟环境 virtualenv myenv_py2 # 激活环境 source myenv_py2/bin/activate激活之后命令行的提示符前面会出现(myenv_py2)字样。这时候你用python、pip操作的都是这个虚拟环境里的版本跟外面的系统环境彻底隔离了。4.3 Python 3项目用venv就够了对于Python 3项目有个好消息Python 3.3及以上版本自带venv模块不需要额外安装第三方工具。python -m venv myenv_py3 source myenv_py3/bin/activate这里稍微解释下venv和virtualenv的区别virtualenv是第三方工具功能更丰富兼容Python 2和Python 3venv是官方自带的精简版只支持Python 3但也够用了。Python 3项目我一般直接用venv不再额外折腾。注意如果你在项目里同时有多个开发者的协作建议在项目根目录添加一个requirements.txt文件把所有依赖固定下来。这样别人通过pip install -r requirements.txt就能快速复现环境。4.4 pyenv-virtualenv的整合用法如果你已经安装pyenv其实可以把virtualenv这层整合进pyenv里用pyenv virtualenv插件来创建虚拟环境。这也是我日常最高效的用法# 用Python 2.7.18创建虚拟环境 pyenv virtualenv 2.7.18 my_py2_app # 用Python 3.8.18创建虚拟环境 pyenv virtualenv 3.8.18 my_py3_app创建出来的虚拟环境在pyenv versions命令里也会看到它会显示为my_py2_app或my_py3_app这样的名字。然后你同样可以用pyenv local把它和项目目录绑定pyenv local my_py2_app这个操作和绑定Python版本的逻辑类似但绑定的是虚拟环境。进入目录后Python和pip自动就是这个虚拟环境的。整个工作流就统一了写pyenv local xxx相当于同时完成了“选Python版本”和“选虚拟环境”这两个动作。团队协作时别人拿到项目文件后只需要一条命令就能进入环境清晰又高效。5. 实操过程中常踩的坑与排查技巧5.1 pip命令装错包装到哪里去了多版本环境下最容易出的问题就是pip装包时不知道装到哪个环境里去了。很多时候你的意图是给Python 2.7的项目装包结果因为没有激活对应的虚拟环境pip install把包装到了全局的Python 3里运行项目时直接ImportError。解决这个问题的方法很简单每次装包前先确认当前环境。which python which pip如果路径指向的是虚拟环境目录比如/home/user/.pyenv/versions/my_py2_app/bin/python那说明环境激活是对的。如果指向的是/usr/bin/python那你现在操作的是系统全局环境要把虚拟环境激活后再装包。如果多个Python版本并存还有一种更稳妥的方法直接用python -m pip代替pip。因为python -m pip会明确使用当前python命令对应的那个环境去装包不会出现“pip和python不同版本”的错位问题。5.2 编译Python时提示找不到ssl模块装完Python后如果运行import ssl报错最可能的原因是编译时缺少libssl-dev或openssl的开发库。从Ubuntu 20.04开始OpenSSL 1.1的dev包是libssl-dev在CentOS 8以上是openssl-devel。可以在安装pyenv依赖时就把这些装上。如果你是在macOS上遇到这个问题很可能是Homebrew安装的OpenSSL没有被Python编译时自动找到。可以用CONFIGURE_OPTS把路径显式指过去CONFIGURE_OPTS--with-openssl$(brew --prefix openssl) pyenv install 3.8.185.3 虚拟环境激活后无法卸载激活虚拟环境后想退出很多人会习惯性敲deactivate命令。有时敲完发现命令提示符前面的环境名没有消失检查发现deactivate命令找不到。这通常是因为环境变量没有正确配置或者虚拟环境的bin目录下脚本不完整。遇到这种情况最暴力的解决办法是直接退出当前shell窗口重新打开一个干净的终端。如果是pyenv virtualenv创建的环境也可以用pyenv shell system来强制切回系统默认环境。实际踩过几次坑之后我现在已经养成习惯只要感觉环境状态不对先echo $PATH看看前面有没有虚拟环境的目录插进来再决定怎么处理。5.4 Windows用户的多版本共存怎么做很多朋友用的是Windowspyenv最初是为Unix-like系统设计的虽然现在有pyenv-win这个社区移植版但在Windows下体验不算完美尤其是在编译Python源码的时候很容易因为缺少C编译器而失败。在Windows下我推荐的方案是直接用Python官方的Windows安装包或者conda。普通开发场景安装Python 2.7和Python 3.8时注意一个关键选项安装Python 3时勾选“Add Python 3.x to PATH”安装Python 2时不要动PATH。然后通过重命名python.exe的方式区分版本比如把Python 2.7安装目录里的python.exe重命名为python2.exe。这样在命令行里python2就是Python 2.7python就是Python 3。用conda在Windows上更省心。它的安装包会把Python、pip和常用科学计算库一起管理好一条conda create -n py27 python2.7就能创建独立的Python 2环境不用手动编译避免了一堆Windows下特有的编译麻烦。6. 从多版本管理延伸到工程实践多版本共存不只是“装好工具”就完了真正到了项目里还需要注意一些工程层面的收尾工作不然开发环境再干净一上线还是会爆炸。6.1 requirements.txt与pip freeze配合使用虚拟环境建好后每次装完依赖我强烈建议立刻执行pip freeze requirements.txt这个命令会把当前环境里所有第三方包及精确版本号导出。下次重建环境时在这个文件所在目录执行pip install -r requirements.txt就能一键还原。Python 2项目最好用pip freeze时加一下--local参数避免导出一些全局依赖。环境里如果安装了editable模式的包freeze导出的内容会带-e前缀这种情况需要手动处理一下再提交。6.2 项目级Python版本统一多人协作的项目最好在项目根目录放一个.python-version文件如果使用pyenv或runtime.txt如果使用Heroku等平台。我刚工作时团队里有人用Python 3.6有人用3.8结果datetime模块的一些行为不一致日志解析的bug排查了好几天。后来直接在项目根目录放上.python-version并提交到Git仓库不管谁拉下来代码pyenv都会自动切换版本再也没出过类似问题。如果团队的机器上没装pyenv其实也不影响因为.python-version只是一个文本文件即使不生效也不影响代码运行只是没法自动切换版本而已。为了减少沟通成本我会在项目README里写清楚要求使用的Python版本和创建虚拟环境的命令。6.3 不要过度追求版本数量最后还有一点想提醒的多版本管理是为了解决实际问题不是为了炫技。如果你手头没有必须用Python 2的项目就别装Python 2如果你没有在多个Python 3小版本之间切换的需求就固定一个版本用到老。环境管理同样有维护成本每多一个版本就多一份需要关注的安全补丁和兼容性测试。Python 2已经停止官方维护很久了如果生产环境还有Python 2第一优先级应该是想办法升级或迁移而不是把开发环境维护得越来越复杂。我在一家老牌公司做维护工作时服务器上最多同时存在过四个Python版本系统自带Python 2.6、业务用的Python 2.7、某个新服务用的Python 3.6和一个用conda装的数据分析环境。四套环境各自独立通过pyenv和virtualenv管理的清清楚楚即使同事离职了新来的人也能通过文档快速上手。这就是“多版本共存”真正该有的样子——不是把环境搞乱而是让每个项目都能在最适合它的Python版本里安稳运行。
返回列表