前言
Ubuntu 上的 Python 之所以容易配乱,是因为「Python」在这台机器上有两个身份:一个是操作系统自己用的那个(包管理器安装的/usr/bin/python3),另一个是你作为开发者要用的那个。前者被系统工具依赖,后者应该由你自己创建和维护。把这两个身份混为一谈,就是各种「装了个包结果系统设置界面打不开」事故的根源。
最常见的误解是「sudo pip install很方便」。它确实方便,代价是往系统解释器里塞进一个 Ubuntu 发行版并不知情、也没测试过的包,可能覆盖系统工具依赖的版本。另一个误解是「装个新版本 Python 就是把老版本替换掉」。实际上你更可能需要的是并存:系统继续用它的,你用你的。
本文按「原则 → 正确做法 → 装新版 → 切换方式与其风险」的顺序讲。必须说明:本机没有 Python 解释器,本文不在 Ubuntu 上,也没有运行过本文任何命令,所有命令与机制均按 Python 官方文档、Linux 手册页与通用发行版约定写出;涉及第三方软件源与第三方工具的部分(例如换个源装新版 Python),请以该来源的官方说明为准。文中不给出任何性能数字。
一、原则:别动系统 Python
系统解释器承担着大量不显眼的工作:包管理器的辅助脚本、桌面环境的某些组件、系统自带的各类工具,都可能依赖/usr/bin/python3以及它site-packages里由发行版打包好的模块。因此有两条铁律:
- 不要往系统解释器里随意装第三方包(尤其是用
pip且加sudo)。 - 不要把系统解释器的指向改到别的版本上。
这两条不是风格建议,而是被官方机制固化下来的规则。较新的 Ubuntu 会启用PEP 668「Marking Python base environments as『externally managed』」:系统解释器被标记为「受管环境」,此时对系统 Python 执行pip install会被拒绝并给出提示。这不是 pip 坏了,而是一道保护,正确的反应是改用虚拟环境,而不是加参数强行绕过。
那要在系统层面装模块怎么办?用发行版的包管理器,也就是apt。Ubuntu 把很多常用库都打包成了python3-xxx形式的包;Python 官方文档在讲 Unix 平台安装附加组件时给的就是这个路子:向用户推荐用系统包管理器安装,例如 Debian 与 Ubuntu 用户用apt,Fedora 与 RHEL 用户用dnf,SUSE 与 openSUSE 用户用zypper,Alpine 用户用apk。
| 你想要的 | 正确做法 | 不要做 |
|---|
| 项目要用第三方库 | 建虚拟环境,在环境内装 | 用sudo pip install装进系统 |
| 系统层面需要一个模块 | apt install python3-xxx | 用pip覆盖发行版打的包 |
| 换个 Python 版本做开发 | 装共存的新版本,用它建环境 | 改掉系统python3的指向 |
| 命令行要用 Python | 写python3,或写具体版本 | 依赖python这个名字 |
二、日常开发的标准姿势:虚拟环境
标准库自带的venv是默认选择(官方文档从 3.5 起就推荐用它创建虚拟环境)。流程只有四步:创建、激活、装依赖、退出。
# 1) 创建环境;目录名约定用 .venv
python3 -m venv .venv
# 2) 激活:POSIX 下激活脚本在 bin 目录里
source .venv/bin/activate
# 3) 在环境内安装,用 python -m pip 而不是裸 pip
python -m pip install --upgrade pip
python -m pip install requests
# 4) 退出
deactivate有两个 Ubuntu 特有的细节要注意:
venv在 Ubuntu 上通常需要单独装一个包。如果创建环境时报出与ensurepip不可用有关的错误,原因多半是发行版把这个组件拆出去了,装python3-venv即可,然后用python3 -m venv重建环境。pip也不一定预装。系统层面若需要,用apt install python3-pip;但在虚拟环境里更应该用python -m pip,它保证「装包的解释器」和「跑脚本的解释器」是同一个。
另一个容易被忽略的点:虚拟环境不是「非激活不可」。官方文档明确说,你可以直接写该环境解释器的完整路径来执行脚本,效果一样。这在与 systemd、cron 这类不经过 shell 的场景配合时特别有用——直接指向.venv/bin/python比想办法让它们「先激活」要可靠得多。
# 适用于 Python 3.8+:进虚拟环境后自查,确认「装」和「用」是同一个解释器
import sys
print("解释器路径:", sys.executable)
print("环境前缀 :", sys.prefix)
print("是否在虚拟环境内:", sys.prefix != sys.base_prefix)
print("site-packages 搜索位置:")
for p in sys.path:
if "site-packages" in p:
print(" ", p)三、需要更新的 Python 版本时怎么办
三条路,各有取舍:
| 路线 | 做法 | 优点 | 风险 |
|---|
| 发行版仓库 | apt install python3.x(仓库里有的版本) | 与系统集成好、有安全更新 | 版本落后于上游 |
| 第三方软件源 | 例如给 Ubuntu 增加一个提供新版 Python 的 PPA,再用apt安装 | 安装方便、可以并存 | 非官方渠道,质量与维护看该来源,以该来源的官方说明为准 |
| 自建 | 用版本管理工具,或从官方源码自行构建 | 完全可控、可并存 | 需要自己维护依赖与升级 |
选择的关键不是「哪个版本最新」,而是「它会不会覆盖系统解释器」。第三方的 PPA 通常会以独立版本号的形式安装(例如装成python3.12而不是替换python3),这才是安全的形态。加源之前务必做两件事:确认该来源确实是你要用的那个、以及确认它安装的是并存版本而不是替换品。具体命令请以该软件源自己的官方说明为准,本文不写死。
装好并存版本后,正确的用法是用它来创建虚拟环境,而不是把它接到系统的python3上:
# 用并存版本的解释器建环境,之后这个项目就一直用环境里的 python
/usr/bin/python3.12 -m venv .venv
source .venv/bin/activate
python -V这样做的结果是:项目之间可以各用各的版本,系统解释器毫发无损。
update-alternatives:用法与风险
update-alternatives是 Debian 系用来管理「同一个通用名对应多个候选实现」的机制。它的手册页讲得很清楚,几个要点值得记住:
- 链接组有两种模式:自动模式下,系统会在安装、卸载软件包时自动决定指向哪一个(按优先级,优先级最高者胜出);手动模式下,系统保留管理员的设置,不再自动改动。链接组第一次被引入系统时处于自动模式。
- 常用命令:
--install link name path priority用于添加一组候选项;--config name列出该组所有候选并让你选,选过之后该组可能就不再是自动模式了,要用--auto才能回到自动;不想交互就用--set;--display name显示该组的模式(auto 或 manual)、当前指向以及所有候选。 - 手册特别强调:提供同一个文件的多个软件包必须协作使用这套机制,也就是说,随意改它属于越权操作。
对 Python 来说,风险非常直白:系统里那个python3的名字背后站着的是一大批系统工具。用update-alternatives把python3指向另一个版本,等于让这些工具去用它们没被测过的解释器。因此:
- 不要用
update-alternatives去改系统python3的指向。需要别的版本,就用那个版本的完整路径去建虚拟环境。 - 如果你确实要管理自己安装的某组命令(比如某几个工具的不同版本),
--install+--config+--display这套流程可以用,并且记得用--display复查当前是自动还是手动模式,别让一次交互选择留下你不知道的手动配置。
四、排障时的固定检查顺序
遇到「这个包明明装了却导入不到」「系统里某个工具突然报错」这类问题,按固定顺序查,比到处试快得多:
- 确认用的是哪个解释器:打印解释器路径与环境前缀,看是不是你以为的那个。
- 确认包装到了哪里:看
site-packages的路径,确认它属于当前环境。 - 确认是不是系统环境被污染:回想有没有用
sudo pip往系统里装过东西;有的话,优先考虑用发行版包管理器把它换成正式包,或者干脆重装那个python3-xxx包恢复状态。 - 确认命令来源:
which一下那个命令,看它指向哪个目录,别把用户目录里装的工具和系统工具搞混。
常见坑点
❌ 直接sudo pip install装包到系统 Python。 ✅ 建虚拟环境再装;系统层面要装就用apt install python3-xxx。
❌ 想用新版本 Python,就去改系统python3的指向。 ✅ 让新旧版本并存,用新版本的完整路径去创建虚拟环境。
❌ 看到externally-managed-environment报错就加参数强行安装。 ✅ 这是 PEP 668 的保护机制,改用虚拟环境;强行绕过等于绕过保护。
❌ 在虚拟环境里用裸pip命令装包,结果装到了别处。 ✅ 用python -m pip,确保装包与运行脚本用的是同一个解释器。
❌ 把.venv目录整个提交到版本库,或用它来「复制环境」。 ✅ 只提交依赖声明与锁文件,环境由命令重建;环境目录写进忽略规则。
❌ 在 systemd 或 cron 里指望 shell 会先激活虚拟环境。 ✅ 直接写虚拟环境内解释器的完整路径,例如项目目录下的.venv/bin/python。
❌ 用update-alternatives --config选完就不管了,没注意它把链接组切成了手动模式。 ✅ 用--display复查模式,需要恢复自动就用--auto。
❌ 把apt装的python3-xxx包再用pip升级一遍。 ✅ 发行版打包的模块交给apt管理,pip只在虚拟环境里活动,两条线不要交叉。
总结
| 场景 | 正确做法 |
|---|
| 日常开发 | python3 -m venv .venv+python -m pip install |
| 系统层模块 | apt install python3-xxx |
| 安装被拒绝 | 这是 PEP 668 的保护,改用虚拟环境 |
| 要新版本 | 并存安装 + 用完整路径建环境 |
| 自动化调用 | 直接指向.venv/bin/python |
| 切换命令指向 | 慎用update-alternatives,绝不动系统python3 |
Ubuntu 上配置 Python 的全部要点可以压缩成一句话:系统解释器只读不动,你的需求全部放进虚拟环境解决。把「系统 Python」和「开发用 Python」这两个身份彻底分开,绝大多数「装了包却用不了」「系统工具坏掉了」的问题就根本不会发生。