Linux 源码编译安装指定版本 Python

系统自带的 Python 是给 apt、yum 这类工具用的,版本由发行版钉死,想升级或装一个仓库里没有的版本,基本只能源码编译。内网隔离环境下更是连 apt install 都连不上仓库,把源码包带进去编译几乎是唯一可靠的路子。

下面是我常用的流程,重点说清楚几个关键点:为什么不用包管理器、--prefixmake altinstall 各起什么作用、离线环境怎么处理依赖。

为什么不用包管理器

Debian/Ubuntu 的 python3 跟发行版绑定,Ubuntu 22.04 默认是 3.10,想跑 3.13,apt 仓库里多半没有。即便有 deadsnakes 这类 PPA,内网也访问不了。

更关键的是系统 Python 不能动。apt、yum 和一堆系统脚本都依赖 /usr/bin/python3,一旦覆盖或改了它的软链接,包管理器先坏,连锁反应能把系统搞瘫。新版本必须装到独立目录,跟系统的彻底隔开。

源码编译能同时解决这两件事:精确指定版本,安装到任意路径,跟系统 Python 互不干扰。

准备工作

  • root 或 sudo 权限
  • 至少 500MB 磁盘空间,编译过程和安装目录都要占地方
  • 确认目标版本号,本文用 Python 3.13.9 演示
  • Debian/Ubuntu 或其他 Linux 发行版均可,依赖包名按发行版调整

下载源码

Python 官方 FTP 按版本归档了所有源码包。

1
2
3
cd /usr/src/
wget https://www.python.org/ftp/python/3.13.9/Python-3.13.9.tar.xz
sudo tar -xf Python-3.13.9.tar.xz

离线场景下,在有网的机器上下好 .tar.xz,用 scp 或 U 盘带进内网即可。源码包本身不依赖外部库,搬运很干净。

安装编译依赖

依赖才是离线编译真正的麻烦点。

Debian/Ubuntu

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
sudo apt update
sudo apt install -y \
build-essential \
libssl-dev \
zlib1g-dev \
libffi-dev \
libbz2-dev \
libreadline-dev \
libsqlite3-dev \
liblzma-dev \
uuid-dev \
tk-dev \
libncurses5-dev \
libncursesw5-dev \
libgdbm-dev \
libnss3-dev

这些库各自干什么

Python 编译时对可选模块的态度是”有头文件就编进去,没有就跳过”,不报错。等你某天 import ssl 失败才发现当初漏装了 libssl-dev,这时候得回头重新编译。所以依赖一次装齐比较省事。

  • build-essential:gcc、make、libc 头文件,编译的基础工具
  • libssl-dev:OpenSSL 头文件,编译出 ssl 模块,pip 走 HTTPS 全靠它
  • zlib1g-dev:zlib 压缩库,zipimport 和 pip 解包要用
  • libffi-dev:libffi,ctypes 模块依赖它,很多 C 绑定库间接需要
  • libbz2-dev:bzip2,bz2 模块
  • libreadline-dev:readline,REPL 的行编辑和历史记录
  • libsqlite3-dev:sqlite3 模块,不少 ORM 和测试框架要用
  • liblzma-dev:lzma 压缩模块
  • uuid-devuuid 模块
  • tk-dev:tkinter
  • libncurses5-dev / libncursesw5-dev:curses 模块
  • libgdbm-devdbm/gdbm 模块
  • libnss3-dev:NSS,部分 SSL 场景用到

漏掉某个 -dev 包,Python 照样编得出来,只是对应的 stdlib 模块会缺失。libssl-dev 尤其不能漏,否则 pip 装包时报 SSL 错误,只能重编。

离线环境怎么搞依赖

真正 air-gapped 的环境,apt install 也连不上仓库。两种做法。

第一种,在一台相同发行版、相同架构、有网的机器上把 .deb 下全再搬进去。用 apt-get install --download-onlyapt download 把上面所有包及其依赖抓下来,传进内网后 sudo dpkg -i *.deb 安装。注意要把依赖的依赖也一起抓全,dpkg 不会自动解决依赖链。

第二种,直接在另一台配置相同的联网机器上按本文流程编译完,然后把整个 /usr/local/python313 目录打包拷到内网机器解压。前提是两边 glibc 和运行库兼容,架构相同、发行版大版本一致。Python 动态链接了 libssl、libffi 等运行库,目标机器上得有对应的 .so,所以这种搬运要求两台机器环境基本一致,不是完全无脑可用。

配置编译选项

1
2
cd /usr/src/Python-3.13.9
sudo ./configure --enable-optimizations --prefix=/usr/local/python313

两个参数是重点。

--prefix=/usr/local/python313 决定安装根目录。所有产物(bin/lib/include/share/)都落在这个路径下。换个 prefix 就能装另一个版本,互不干涉。这是多版本共存的机制基础:不同 prefix,不同目录,各走各的 PATH。

--enable-optimizations 启用 PGO(profile-guided optimization)和 LTO(link-time optimization)。它会先跑一遍采集运行 profile,再据此重新编译,运行时性能有实打实的提升,但编译时间会明显变长,因为要跑两遍。生产服务器值得开,快速验证或 CI 里想省时间可以去掉。

千万不要把 --prefix 指向 /usr/usr/bin,也别省略 prefix 用默认的 /usr/local 再跑 make install,那会往系统目录写 python3 软链接。装到独立的 /usr/local/python3XX 是最安全的做法。

编译和安装

1
2
sudo make -j$(nproc)
sudo make altinstall

make -j$(nproc) 用满所有 CPU 核心加速编译。开了 --enable-optimizations 的话,整个编译加安装大概 5 到 15 分钟,看机器。

make altinstallmake install 的区别是这篇文章最该记住的一点:

  • make install 会创建 python3pip3 这类不带版本号的软链接,直接指向新装版本,可能盖掉系统的 python3,危险。
  • make altinstall 只装带版本号的二进制(python3.13pip3.13),不碰 python3 软链接。

记住一条:永远用 altinstall,让系统 Python 安安静静待在原地。

验证安装

1
2
3
cd /usr/local/python313/bin
./python3.13 --version
./pip3.13 --version

预期输出类似:

1
2
Python 3.13.9
pip 24.x from /usr/local/python313/lib/python3.13/site-packages/pip (python 3.13)

再配一下 PATH,让任意目录都能调用。编辑对应的 shell 配置文件,zsh 改 ~/.zshrc,bash 改 ~/.bashrc

1
export PATH="/usr/local/python313/bin:$PATH"

重载配置后验证:

1
2
3
source ~/.zshrc
python3.13 --version
which python3.13

which 应该输出 /usr/local/python313/bin/python3.13。PATH 里把新版本的 bin 放前面,调用 python3.13 走新装的,但 python3 仍然指系统版本,互不影响。

多版本共存

给每个版本一个独立 prefix 就行,互不干扰:

1
2
3
4
5
6
7
8
9
10
11
# Python 3.11
./configure --enable-optimizations --prefix=/usr/local/python311
make -j$(nproc) && sudo make altinstall

# Python 3.12
./configure --enable-optimizations --prefix=/usr/local/python312
make -j$(nproc) && sudo make altinstall

# Python 3.13
./configure --enable-optimizations --prefix=/usr/local/python313
make -j$(nproc) && sudo make altinstall

三个版本分别落在三个目录,各自的 python3.11python3.12python3.13 共存。把三个 bin 目录都加进 PATH,就能随时切换。比 pyenv 轻量,代价是要自己管理 prefix 和 PATH。

项目级隔离交给 venv:

1
2
python3.13 -m venv myproject_env
source myproject_env/bin/activate

卸载

编译安装没有卸载脚本,但因为是独立 prefix,直接删目录就干净了:

1
sudo rm -rf /usr/local/python313

再把 shell 配置里对应的 PATH 行删掉。venv 目录是独立的,跟 Python 安装本身无关,删不删看你自己。

几个容易踩的坑

编译过了但 import 模块失败。 编译时漏装了对应的 -dev 包,模块被静默跳过。import sslimport ctypesimport sqlite3ModuleNotFoundError 基本都是这个原因。补装依赖后重新 configure && make && make altinstall

pip 报 SSL 错误。 libssl-dev 没装就编译,ssl 模块缺失。装上 libssl-dev 重编即可。

系统工具突然异常。 大概率是用了 make install,或者把 prefix 指到了 /usr 下,python3 软链接被覆盖。用 altinstall 加独立 prefix 能完全避免。

编译太慢。 --enable-optimizations 要跑两遍,CI 或快速验证时去掉它,能省一大半时间。