上周在 Termux 里跑一个三年前写的数据清洗脚本,第一次意识到版本差距不是小打小闹。脚本里那个老旧的gensim模型在两年前的 Python 3.8 上跑得稳稳当当,换到 Python 3.11 直接抛AttributeError,再换 3.12 干脆连import都过不去。翻遍 issue 最后结论出奇一致:这库没人维护了,只认 3.8。于是我被逼着在 Termux 里执行降级安装 python3.8——不是装个新包然后pkg install python3.8能完事,而是要把旧解释器完整地落到 Android 的终端环境里。这篇文章专门聊这件事:降级的两条可行路线、每条路线要跨的坎,以及折腾过程中容易被忽略的编译和依赖问题。如果你手头也有项目必须跑在 python3.8 上,或者正在被其他 Python 新版本兼容性问题折磨,按下面的流程走一遍,大概率能少交两小时学费。
很多人以为 Termux 是“Android 上的 Ubuntu”,包管理操作应该和桌面版差不多,实际差距很大。Termux 的仓库策略、编译环境、链接库路径都有独特的一套,直接套用 Linux 教程的降级方案,多数时候不仅装不上,还会把现有 Python 环境搅成一锅粥。所以我不打算只给一条命令,而是把背后的原因和实操取舍都讲清楚。
1. 为什么 Termux 降级 Python 这么费劲:包管理器的“单向车道”
1.1 Termux 的 apt 仓库里只有一个 Python 大版本
Termux 基于 apt 管理软件包,但它是一个 Android 应用沙箱里的 Linux 环境,所有软件都安装到$PREFIX,通常也就是/data/data/com.termux/files/usr。这套设计决定了它对「多版本共存」的支持非常弱:官方仓库出于维护成本考虑,对同一门语言往往只保留一个活跃的大版本。
你可以自己在终端里验证一下:
pkg install python python --version目前新装的 Termux 默认拉到的基本是 Python 3.11 或 3.12,取决于仓库快照时间。如果你想直接执行:
apt install python3.8大概率会得到Unable to locate package python3.8。这不是你包名拼错了,而是仓库里压根不存在这个版本的安装包。Ubuntu 上能apt install python3.8,是因为有较老的系统仓库或 PPA 支撑;Termux 官方没有这套历史存档机制,眼睁睁给你一个“最新版”之外的选项都不给。
更要命的是,Termux 的 Python 是经过 Bionic libc 适配的,它自己带了一堆针对 Android 的编译补丁。直接从 Python 官网拉一个 Linux 的二进制或源码包扔进去,大概率跑不起来,因为依赖的动态库和系统路径都被重新定义了。所以“降级安装”在这里不是一句简单的 apt 语法,而是一次对环境底层的重构。
1.2 降级的三个真实可行思路:源码、旧 deb、版本管理器
网上关于“Termux 降级 python3.8”的教程不少,我实操和对比下来,能落地的基本是三条线:
| 方案 | 优点 | 缺点 | 适合场景 |
|---|---|---|---|
| 源码编译安装 | 版本完全可控,最「干净」 | 耗时长、内存吃紧、要自己排编译错 | 长期使用、需要跑多种 C 扩展 |
| 旧 .deb 包硬装 | 看起来最快,几条命令搞定 | 依赖极容易冲突,装完环境可能崩 | 临时跑一个短脚本,能快速验证 |
| pyenv 版本管理 | 多版本切换方便,结构清晰 | 在 Termux 上底层还是要编译,且路径适配坑更多 | 需要频繁切版本,且设备内存充裕 |
我最后采用的是源码编译这条主线,先整体把 Python 3.8 装好,再用虚拟环境固定项目依赖,并在第 3 章补充旧 deb 路线的适配场景和止损策略。下面先把最核心的源码编译讲透。
2. 源码编译 Python 3.8:依赖准备和 configure 抉择
2.1 先别急着 configure:把编译链和依赖一次性装齐
在 Termux 里编译 Python,不是你有 clang 就行。Python 标准库在编译过程中会检测一堆底层组件,比如 SSL、ffi、zlib、sqlite,任何一个缺失都可能导致最终的二进制功能残缺——最典型的是编译完import ssl报错,或者_ctypes模块直接消失。
我建议在开始前,先把依赖一次性装齐:
pkg update && pkg upgrade pkg install clang make pkg-config openssl bison sqlite libffi zlib ncurses这里每个包的解释都值得看一眼:
clang:Termux 的默认编译器是 Clang 而不是 GCC,记住这一点,很多 Linux 教程会让你apt install gcc,在 Termux 里方向就错了。后续编译依赖时,CC环境变量也默认指向 clang。make:Python 源代码的构建系统,没有它一切免谈。pkg-config:configure 脚本需要用它来探测openssl、libffi等库的头文件和链接路径。openssl:提供 SSL/TLS。老教程会写openssl-dev,但新版 Termux 仓库里openssl包已经自带头文件,openssl-dev早就不单独存在了。装完可以检查/data/data/com.termux/files/usr/include/openssl/ssl.h是否存在。bison:Python 语法分析器的生成器,缺失时编译会在Parser/pgen阶段卡住。libffi、sqlite、zlib、ncurses:分别对应_ctypes、_sqlite3、zlib压缩支持、终端库等核心模块。如果不装全,之后你会不断遇到某个标准库import失败。
这些包加起来体积不小,但在 Android 上其实还好,优先保证一次到位。
2.2 下载源码版本,configure 参数逐个拆解
Python 3.8 的源码建议选择 3.8 分支的最后一个小版本,我装的是 3.8.20,包含后续的安全修复,避免一上来就带着一堆已知漏洞。下载解压:
wget https://www.python.org/ftp/python/3.8.20/Python-3.8.20.tgz tar -xzf Python-3.8.20.tgz cd Python-3.8.20有人问为什么不用git clone?CPython 的 Git 仓库带了一堆子模块和分支,体积大不说,在 Termux 的受限网络下还容易 clone 到一半断掉。直接拉官方 tar 包最干净。
接下来是重头戏 configure :
./configure --prefix=$PREFIX --enable-optimizations --with-system-ffi --with-system-expat逐条解释:
--prefix=$PREFIX:让 Python 安装在 Termux 的 usr 目录下,和系统里其他 apt 包装在一起。不要试着指定自定义路径到/data/local,Android 的沙箱权限会拒绝写入。--enable-optimizations:启用 PGO 优化,让解释器运行更快。代价是编译时间几乎翻倍,占据的内存也更高。如果你只是跑纯 Python 数据处理,可以不加;但我首次降级时加了它,后续跑模型推理时明显更稳。--with-system-ffi:让 Python 使用系统 libffi 而不是内部捆绑版本。Termux 的编译环境有时无法加载内部 libffi 的符号,所以这个参数一定要带上,否则_ctypes大概率编译失败。--with-system-expat:同理,让 Python 使用系统的 expat XML 库,避免捆绑版本和 Android 的 libc 不兼容。
有不少教程会额外建议--disable-shared,让 Python 以静态方式把运行库编进可执行文件,这样python3.8命令不会在运行时找不到libpython3.8.so。但我实测下来并不推荐,原因是如果你后续要装一些需要加载.so扩展的第三方库,静态编译的 Python 支持度很差。我宁可让动态库正常生成,再去解决链接路径问题,后面第 5.3 节会专门讲。
2.3 make 的并发数、OOM 规避和 make install 的副作用
configure 完成后就是编译。这里有个针对 Android 设备的特殊建议:不要无脑make -j$(nproc)。手机的内存和桌面机截然不同,2GB 内存的机器跑-j4,编译到一半进程就被系统杀掉,你只能从头再来。我自己的实测经验是:
- 2GB 内存:
make -j2极限,能跑完,耗时约 15~20 分钟。 - 4GB 及以上:
make -j4可以尝试,但建议先free -h看一眼剩余内存。
命令:
make -j2 make installmake install这一步的副作用是你必须提前想清楚的。如果 Termux 里已经通过pkg install python装过官方 Python,这次安装会在/data/data/com.termux/files/usr/bin/下生成一堆同名或相近的命令——比如python3.8、pip3.8。它不会主动覆盖python3软链,但会把python3.8这个新的可执行文件放进去,和你现有的官方 Python 共存。这一点既是好事也是坏事:好的是你不必忍痛pkg remove python,坏的是如果你后续手动把python3指向 3.8,系统里其他依赖官方 Python 的命令行工具就会遭殃。我的建议是:编译安装完先不要动,用独立的python3.8命令去运行项目,保持共存。如果你真的要完全替换默认 Python,等确认 3.8 可用后再执行pkg remove python,然后手动维护软链,这样回滚还能有退路。
3. 用旧 deb 包硬降级:省力路线,但依赖冲突是老大难
3.1 哪里能翻出 python3.8 的旧 .deb
如果你只是想临时跑一个旧脚本,不想等十几分钟编译,另一条路线是从历史仓库里翻出 python3.8 的.deb文件直接dpkg -i。Termux 官方仓库的历史包其实并没有被删除,你可以在包索引的 pool 目录下按名字找,例如:
https://packages.termux.dev/apt/termux-main/pool/main/python/文件名大致长这样:python_3.8.x_arm64.deb。要说明的是,Termux 官方仓库的包名是python而不是python3.8,所以下载前最好先dpkg -I xxx.deb确认包名、版本和依赖关系,否则你可能误把一个 3.8 的 python 包当成 python3.8。
除了官方历史文件,还有社区维护的 tur 仓库(Termux User Repository),里面的python3.8包会更完整,但第三方仓库的依赖名可能与官方不完全一致,混用后pkg update时有概率报冲突。我建议把第三方来源仅当作“应急补丁”,优先找官方路径。
3.2 dpkg 强制安装的完整步骤与回滚策略
假如你已经拿到了一个版本匹配的python_3.8.x_arm64.deb,执行:
wget https://.../python_3.8.x_arm64.deb dpkg -i python_3.8.x_arm64.deb如果依赖不满足,会出现典型的dpkg: dependency problems报错。很多人第一时间想到的是--force-depends:
dpkg -i --force-depends python_3.8.x_arm64.deb这样能强行把包装上,但风险是 Termux 的 dpkg 数据库会被“污染”——它会认为 python3.8 已就位,而实际上某些依赖库版本不对。之后你再装任何与 Python 相关的包,都会触发连锁依赖检查,甚至其他命令行工具崩掉。
所以如果你决定走硬装,请先做好三件止损准备:
- 执行前备份
$PREFIX/bin/python*和$PREFIX/lib/python3.*到~/python_backup。 - 执行前记录当前与 Python 相关的包列表:
dpkg -l | grep -i python。 - 装砸了之后的回滚命令是
apt reinstall python,再手动恢复备份文件。
我在一台旧机上试过,贪图省事直接硬装了一个老 deb,然后pip命令就找不到python3.8的位置,连python -V都开始反复横跳。最后还是回到源码编译才把环境救回来。
3.3 为什么我不推荐直接卸载系统 python
有些教程会说“既然要降级,先pkg remove python再装 3.8”。这句话听着干净利落,实际上在 Termux 里极容易引发连环事故。因为系统里很多基础工具的管理脚本、pkg自身的后处理脚本、甚至部分插件都隐式依赖 python3 的某些运行环境。你把官方 Python 卸载,那些工具不一定立刻崩,但等它们需要调用 python 来执行一段辅助脚本时,就会因为找不到解释器而报错。
所以请记住:降级安装 python3.8,不代表非要把默认 python 干掉。最好的“降级”是共存隔离,让旧版解释器在独立路径下稳定运行,而不是把整个 Termux 环境改造成只有一个 Python 的“干净状态”。
4. 降级后的版本隔离:别让 python3.8 把系统环境搅乱
4.1 prefix 编译后生成哪些命令,和系统 python 如何共存
完成源码安装后,$PREFIX/bin下通常会出现python3.8、pip3.8、idle3.8等以版本号命名的命令。这些命令和官方包生成的python3、pip3是两个独立实体,互不干扰。如果你在终端里直接输入:
python3.8 -V应该能看到Python 3.8.20。此时python3 -V大概率还是系统自带的 3.11/3.12,两个命令井水不犯河水。
如果你希望叫得更顺口,可以直接做软链:
ln -s $PREFIX/bin/python3.8 $PREFIX/bin/python38注意:不要轻易把$PREFIX/bin/python3的软链改成指向 python3.8。因为python3这个文件由官方包管理,你手动改它之后,任何一次pkg upgrade都可能因为文件哈希不匹配而报错,还会影响其他脚本的 shebang(很多工具的脚本第一行是#!/usr/bin/env python3)。
4.2 建虚拟环境的最佳时机:venv 创建和 pip 配置
降级成功不等于项目能跑,真正让项目跑起来的是虚拟环境。即使你有多个 python3.8、python3.12,如果直接在全局 site-packages 里装依赖,迟早会因为版本错乱把自己绕晕。项目目录里执行:
python3.8 -m venv venv38 source venv38/bin/activate pip install --upgrade pip setuptools这里有个细节:Python 3.8 在创建 venv 时,需要能找到一个可用的初始化基础环境。如果你的 Termux 里没有任何 python3,或者官方 python 也没装,venv创建有可能报错。所以我建议始终保留系统的官方 python,即使你在项目里只用 3.8。
Pip 安装第三方包时,如果网络慢,可以在项目目录下配置pip.conf指到国内 PyPI 镜像:
[global] index-url = https://pypi.tuna.tsinghua.edu.cn/simple trusted-host = pypi.tuna.tsinghua.edu.cn这只是换源,不影响 Python 版本选择,但能让那些动辄几百 MB 的依赖下载时间从半小时压缩到几分钟。
4.3 alias 与 PATH 调整的稳妥做法
如果你嫌每次都打python3.8太啰嗦,可以在~/.bashrc里加 alias:
alias python3.8=$PREFIX/bin/python3.8 alias pip3.8=$PREFIX/bin/pip3.8为什么要用 alias 而不是软链?因为 alias 只影响你交互终端的输入,不会改变其他脚本解析 shebang 时的行为。你发出去的脚本第一行如果写了#!/usr/bin/env python3,它依然会去找系统 python3,不会因为你 alias 了python3.8而意外跑错解释器。这样既享受了旧打字手感,又不污染全局执行环境。
5. 编译实战中的四个经典报错和排查思路
5.1configure提示C compiler cannot create executables
这个报错几乎每次都能在 Termux 编译贴里看到。最常见原因是 clang 没装对位置,或者环境变量里残留了不合适的CFLAGS。很多老教程会建议export CFLAGS="-m64",这在旧的 Android 环境上也许有用,但现在 Termux 完全靠 configure 自动识别架构,你手动设置反而会破坏它。
检查方式:
which clang clang --version env | grep CFLAGS如果发现 CFLAGS 或 LDFLAGS 有残留值,直接unset CFLAGS LDFLAGS,然后重新 configure。同时记得把之前未完成的编译目录清理干净,重新解压源码再走一遍,避免config.cache里的脏数据干扰判断。
5.2make时_ssl模块构建失败的根因
编译完成并安装后,如果你进入python3.8执行:
import ssl然后报ModuleNotFoundError: No module named '_ssl',这就是编译阶段 OpenSSL 检测失败的典型表现。原因通常是 Termux 的 OpenSSL 版本升到了 3.x,而 Python 3.8 的源码里某些调用接口不够兼容,或者 configure 根本没找到头文件。
排查思路:
grep -i ssl config.log | head -50看 configure 是否输出了checking for X509_VERIFY_PARAM_set1_host... yes。如果 no,就说明 OpenSSL 头文件没被识别。
解决办法是回到依赖安装那一步,执行:
pkg reinstall openssl然后删除源码目录里的 build 缓存,重新 configure、make。千万不要只装一个libssl-dev之类的包,Termux 仓库里根本没有这种包名。
5.3make install后python3.8依然找不到libpython3.8.so
如果你 configure 时保留了--enable-shared(默认),安装完成后的python3.8是一个动态链接的可执行文件,运行时需要找到$PREFIX/lib/libpython3.8.so.1.0。Termux 不像桌面 Linux 那样有权限执行ldconfig,所以系统默认不会主动加载这个新库。现象就是:
$ python3.8 error while loading shared libraries: libpython3.8.so.1.0: cannot open shared object file解决办法是在~/.bashrc中加上:
export LD_LIBRARY_PATH=$PREFIX/lib然后重新打开一个终端,python3.8就能正常启动。这一步几乎每个人都会遇到,因为 Termux 的链接器搜索路径并不包含$PREFIX/lib的全部动态库。如果你非常不想用动态库,可以回到 configure 阶段加--disable-shared,但前面说过,后续第三方扩展的 .so 加载会受限,我建议还是用动态库加环境变量的组合。
5.4pip install编译扩展时编译器报错
有了 Python 3.8 之后,用pip install安装带 C 扩展的包时,你可能会看到类似gcc: command not found或g++: command not found的错误。Termux 里面根本没有 gcc,只有 clang。
解决办法在 configure 阶段就做好铺垫:在用./configure之前,先告诉编译系统我们后续要用 clang:
export CC=clang export CXX=clang++这样 configure 和后续 setuptools 构建扩展时,自动继承这个编译器选择。如果环境变量已经设晚,可以强制重装一个具体扩展:
pip install --force-reinstall --no-cache-dir <包名>同时确认pkg install binutils make已经装好。大多数纯接口的 C 扩展,在这套组合下都能顺利编译通过。
最后再分享一点我自己的体会:如果你只是临时想在一台已经升级到 Python 3.12 的 Termux 上“降级”跑个旧脚本,最省心的方法不是立刻开始编译,而是先想想那个脚本能不能用 Docker 容器或者云上的一台 3.8 环境先跑通。因为 Termux 的 Python 3.8 源码编译虽然能成功,但它毕竟是 Android 适配环境,库版本和桌面 Linux 还存在细微差别。要是脚本里调用了太多系统层面的绝对路径,即使你在 Termux 里降级到 3.8,也未必能直接跑。真正愿意折腾 Termux 降级的人,多半是需要在手机上离线处理数据,或者和我一样对旧库有一种“改代码不如改环境”的执念。只要把版本隔离做好,编译流程走顺,这套环境其实是相当稳定的——我目前的主力机就用python3.8挂着两三个数据分析任务,运行了整整一个月没有崩过。