Python 编辑器上架鸿蒙 PC:从 Thonny 真编译移植到 UI 升级版的完整技术解析
Python 编辑器上架鸿蒙 PC:从 Thonny 真编译移植到 UI 升级版的完整技术解析
欢迎加入开源鸿蒙 PC 社区:https://harmonypc.csdn.net/
欢迎在 PC 社区平台申请新建项目:https://atomgit.com/OpenHarmonyPCDeveloper
适配开源地址:https://atomgit.com/OpenHarmonyPCDeveloper/ohos_thonny
源于开源 Python IDE Thonny,游于鸿蒙——安装即用,开启你的 Python 之旅。
一、写在前面:这次上架的是「UI 升级版」
「青蟒 Python 编辑器」(英文名 Thonny Native,包名 org.thonny.native)已经在 HarmonyOS PC 应用市场上架。这里首先要向已经用过 V1.0 的读者说明一点:本次上架的版本是一个 UI 升级版,基于 V1.0 的架构与样式做了全面改进——底层解释器链路(交叉编译 CPython 3.12.9 + 上游 Thonny 4.1.7 源码)保持不变,而前端从「验收工具页」升级成了一个完整的 IDE 外壳:多标签代码编辑器、Python 语法高亮、交互式 Shell、变量面板、文件打开/保存、关于对话框,全部重做。
具体来说,两个版本的关系是这样的:
| 维度 | V1.0(1.0.5,内部代号 code-runner) | 上架版(1.1.0,内部代号 ide-shell) |
|---|---|---|
| 定位 | 移植链路验收工具 | 可日常使用的入门级 Python IDE |
| 界面 | 两个页签:「运行日志」+「Python 执行器」 | 多标签编辑器 + Shell 面板 + 变量面板 + 菜单栏 |
| 启动内容 | 预置示例代码 | 启动即空白 untitled.py,不给用户预置任何东西 |
| 语法高亮 | 无 | 逐行词法着色,配色逐项对照上游 Thonny 主题 |
| 交互 | 单次执行,回显 stdout/stderr | 交互式 Shell,支持 input()、exit()、%Run |
| 变量观察 | 无 | 变量面板,每次执行后自动刷新 |
| 文件 | 无 | 打开 / 保存 / 另存为 / 多标签(DocumentViewPicker,零权限) |
| 底层 | 交叉编译 CPython 3.12.9 静态嵌入 + Thonny 4.1.7 源码 | 完全相同 |

换句话说,V1.0 回答的问题是「真实的 CPython 和真实的 Thonny 能不能在鸿蒙上跑起来」;而上架版回答的问题是「跑起来之后,能不能像一个真正的 IDE 一样被初学者使用」。这篇文章会把两个版本的技术方案一次讲透:为什么坚持「真编译」路线、静态嵌入解释器的完整链路、UI 升级版每一块功能的实现细节,以及一路上踩过的坑。

二、为什么是「真编译」
市面上的「XX 平台移植 Python」方案,相当一部分实际做法是:自己用目标平台的 UI 框架画一个编辑器界面,再加一个远端解释器或者干脆做个假的执行反馈。这种做法 UI 看起来像,但解释器不是真的,生态(pip、第三方库、Thonny 的教学功能)更是无从谈起。
我们在这个项目上立的第一条红线就是:不做 UI 仿制,做真编译移植。目标是让真实的运行时栈在鸿蒙上跑起来:
- 真实的 CPython 3.12.9——不是套壳、不是 JS 模拟,而是用 OHOS NDK 的 clang 交叉编译出来的 aarch64 原生解释器;
- 真实的上游 Thonny 4.1.7 源码——直接 vendor 上游 GitHub 仓库的 Python 包,不改一行逻辑;
- 验收标准不看出错与否,看输出内容——
PyRun_SimpleString返回 0 不代表成功,必须 stdout 里出现约定标记(Hello from real CPython on OHOS!、thonny 4.1.7、common_ok True)才算通过。
这条路线在工程上要回答三个问题:
第一,鸿蒙应用沙箱不允许 exec。 应用的 filesDir 是 noexec 挂载,把 python3.12 可执行文件丢进沙箱再拉子进程这条路是死的。所以解释器不能以独立进程形态存在,只能以静态库形式链进 NAPI 的 .so,在应用进程内直接调用 Py_InitializeEx / PyRun_SimpleString。
第二,标准库和 Thonny 源码不是代码,是数据。 lib/python3.12 下几百个 .py 文件本身不需要执行权限,Python 解释器只是把它们当文本读进来编译成字节码。所以它们可以作为纯数据随 HAP 分发:打包成 zip 放在 rawfile 里,首次启动解压到 filesDir/ohos_python/,然后通过 PYTHONHOME / PYTHONPATH 指过去。
第三,C 扩展模块怎么办。 静态链接的 libpython 加载不了 lib-dynload 下的 math.so、_ssl.so 这类动态扩展(dlopen 找不到共享的 libpython 符号)。这是一个诚实的取舍:交叉编译阶段裁掉 _ssl、_hashlib、_ctypes、_tkinter、_sqlite3 等依赖缺失或意义不大的模块,保证纯 Python 路径 100% 可用。对入门教学场景(这也是 Thonny 的定位)来说,这个裁剪是可以接受的,并且我们在文档里明确标注了这一限制,而不是含糊其辞。
这三个决策确定了整个项目的形态,后面所有工作都是在这个骨架上添砖加瓦。
三、整体架构:一张图说清楚

分层来看:
- ArkTS 层(
Index.ets,约 1400 行):负责运行时 zip 的解压与校验、IDE 界面的全部渲染与交互,以及通过 NAPI 桥调用解释器。上架版的全部 UI 升级都发生在这一层。 - NAPI 层(
thonny_napi.cpp,约 390 行):极薄的桥。对外只暴露 4 个方法:getBuildInfo/setPythonHome/runTest/runCode。内部持有g_pyInited标志保证解释器全生命周期只初始化一次,所有执行入口共用一把 mutex 串行化。 - CPython 层:交叉编译产物
libpython3.12.a(33MB)以-Wl,--whole-archive整体链入 NAPI .so。whole-archive 是必须的——静态库链接默认只取「被引用的目标文件」,而解释器的很多模块是运行时才被 import 的,不 whole-archive 会链接期看着没问题、运行期 import 即崩。
CMakeLists 的核心就这几行,值得完整贴出来:
set(PYTHON_INC "${CMAKE_CURRENT_SOURCE_DIR}/python_inc")
set(PYTHON_LIB "${CMAKE_CURRENT_SOURCE_DIR}/prebuilt/libpython3.12.a")
add_library(thonny_native SHARED thonny_napi.cpp)
target_include_directories(thonny_native PRIVATE "${PYTHON_INC}")
# whole-archive: pull full interpreter from static archive into the NAPI .so
target_link_libraries(thonny_native PUBLIC
-Wl,--whole-archive
"${PYTHON_LIB}"
-Wl,--no-whole-archive
libace_napi.z.so
libhilog_ndk.z.so
libc++.a
libm.a
libdl.a
)
还有一个细节:add_library(thonny_native ...) 而不是 add_library(libthonny_native ...)——前者产物是 libthonny_native.so,后者会变成双 lib 前缀的 liblibthonny_native.so,ArkTS 侧 import thonnyNative from 'libthonny_native.so' 直接加载失败。这是鸿蒙 NAPI 开发的高频坑,我们把它写进了 README 的必读清单。
四、V1.0 回顾:先用最朴素的界面证明链路
在讲 UI 升级版之前,有必要回顾 V1.0 做了什么,因为上架版的所有底层逻辑都原封不动继承自它。
V1.0 的界面非常朴素:一个「运行日志」页负责 P2 验收(点 Run Test 按钮,跑一段固定的验证脚本),一个「Python 执行器」页让用户输入任意 Python 代码并回显结果。

点下 Run Test 后,状态栏从 P2 Ready 变成绿色的 P2 SUCCESS,日志里依次出现:
Hello from real CPython on OHOS!
python 3.12.9
thonny 4.1.7
common_ok True
[P2] SUCCESS: real CPython imported real Thonny 4.x source (headless).

这段验证脚本是精心设计的「最小充分集」:
import sys; print(sys.version)—— 证明解释器本体活着,而且是 3.12.9;import thonny; print(thonny.get_version())—— 证明上游 Thonny 源码在 site-packages 里被真实加载;import thonny.common—— 证明 Thonny 的内部模块依赖链可以走通;print(1+1)、三行count、Done.—— 证明 stdout 捕获管道逐行可靠,不是碰巧拿到一行输出。
C++ 侧的判定逻辑也很直接:报告字符串里必须同时命中 PyRun_SimpleString rc=0、Hello、thonny 4.、common_ok True、count、Done 全部标记,才追加 [P2] SUCCESS。任何一项缺失都会给出具体的失败提示,甚至包括「runtime zip 没解压对,缺哪个文件」的 hint。
执行器页则是把 runCode() 接到输入框,可以跑任何纯 Python 代码。下面这张图是跑了一段斐波那契平方数列的输出,500 多个数字一气呵成:

V1.0 在 HUAWEI MateBook Pro(HarmonyOS 7.0.0(26.0.0))真机上完成全部验收:应用 367ms 拉起、P2 SUCCESS、7 行示例代码 2ms 跑完、退出重进日志出现 reuse existing(解压产物正确复用)。这个版本证明了「交叉编译解释器 + 上游 Thonny 源码」的链路成立——但它毕竟只是一个验收工具,界面离「能给初学者写代码」还差得远。UI 升级版要补的正是这一段。
五、UI 升级版:每一处样式改动都有出处
上架版最重要的设计原则是:UI 可以用 ArkUI 重写,但视觉规范不许自创。Thonny 是一个有十多年积淀的教学 IDE,它的配色、布局是被无数初学者验证过的。所以我们把上游源码翻出来,逐项对照着做:
- 窗口框架色取自
base_ui_themes.py的 clam 主题:菜单栏/工具栏#dcdad5、未选中标签页#cfcdc8、边框#bab5ab、悬停#eeebe7; - 编辑器底色取 Thonny 标志性的米黄
#ffffdc; - 语法高亮取
base_syntax_themes.py的 default_light 主题:关键字/内建#7f0055、数字#B04600、字符串 DarkGreen、注释 DarkGray、def/class定义的名称 DarkBlue 加粗; - Shell 的提示符紫
#A020F0、stdin 蓝#0000FF、stderr 红#CC0000、%Run行用超链接蓝#3A66DD; - 连 Shell 顶部的黄色通知条(
#ffff99,对应上游shell.py的 notice 条)都保留了。
上架版主界面长这样——上面是 clam 风格的菜单栏与标签页,中间是米黄底的编辑器与行号槽,下面是 Shell 面板:

截图中跑的是一段去重函数,Shell 里 >>> 提示符、紫色的 %Run untitled.py(对应上游 Thonny 执行脚本时的 magic 命令样式)、结果 [1, 2, 3, 4]、底部新的 >>> ,交互形态与桌面版 Thonny 的 Shell 一致。
5.1 多标签编辑器与行号槽
编辑器按「标签页 + 行号槽 + 编辑区」组织。每个 EditorTab 持有 id / name / path / content / dirty 五个字段:path 为空表示未保存的新文件,dirty 驱动标签页标题前的 * 号。打开一个已经在标签页里存在的文件时直接切过去,不重复开页。
行号槽有一个容易被忽略的细节:空白区域也要补行号。大多数粗制编辑器只在有内容的地方画行号,往下滚动就断了。我们的做法是把内容行数和视口容量取大(editRows()),行号槽按 22vp 的固定行高一直铺到视口底部,和正规 IDE 的观感一致:
editRows(): number {
const lines = this.editContent.split('\n').length;
return Math.max(lines, Math.floor(this.editorViewportH / EDITOR_ROW_H));
}
editorLines(): string[] {
const raw = this.currentTab()!.content.split('\n');
if (raw.length > 1 && raw[raw.length - 1].length === 0) {
raw.pop(); // 末尾换行不产生幽灵行
}
const minRows = Math.max(raw.length, Math.floor(this.editorViewportH / EDITOR_ROW_H));
while (raw.length < minRows) {
raw.push(''); // 空白区域也补行号
}
return raw;
}
5.2 语法高亮:一个 80 行的逐行 tokenizer
语法高亮没有引入任何第三方库,就是一个手写的逐行词法着色器。原因是 Python 的单行着色规则足够简单(注释、字符串、数字、标识符四类 token),而引入依赖要付出包体和兼容代价。实现上按字符扫描,处理四类分支:
tokenizeLine(line: string): Token[] {
// 1. '#' 起始 → 整行剩余部分都是注释(SYN_COMMENT)
// 2. 空白 → 原样保留
// 3. 引号 → 扫到配对引号(处理 '\' 转义),整段标 SYN_STRING
// 4. 数字 → 扫十六进制/八进制/二进制前缀、小数点、下划线,标 SYN_NUMBER
// 5. 标识符 → 查两张表:
// prevWord === 'def' || 'class' → SYN_DEFINITION 加粗(函数/类定义名)
// 命中 KEYWORDS(37 个关键字) → SYN_KEYWORD 加粗
// 命中 BUILTINS(58 个内建) → SYN_BUILTIN
// 其他 → 默认前景色
}
其中「def/class 后面的标识符单独着色并加粗」是靠维护一个 prevWord 状态实现的——这恰恰是上游 Thonny 编辑器里的定义名高亮行为,也是很多简易高亮器漏掉的细节。所有色值常量都注释了出处(对应上游主题文件的哪个字段),方便将来跟着上游版本同步。
5.3 Shell 面板:input() 和 exit() 是两个深坑
Shell 是上架版工作量最大的一块。交互式 Shell 要处理三类输入:
- 普通语句——直接
runCode()执行,回显 stdout/stderr; - 脚本中的
input()——嵌入式解释器没有交互 stdin,input()会直接EOFError; - 用户敲
exit()/quit()——真让它生效,整个应用进程就没了。
第 2、3 类是真正的深坑,值得展开讲。
input() 的方案:stdin 重放 + 哨兵协议。 思路是:脚本里出现的 input(),由我们在执行前覆盖 builtins.input 来接管。每次执行前,把 Shell 里已收集到的 stdin 行注入 __stdin_lines__ 列表;有剩余行就弹出返回(实现重放,比如脚本里连续两个 input(),用户提前把两行都敲好);没有剩余行时,打印一个哨兵 __NEED_INPUT__:<prompt> 并抛出 _NeedInput 异常。ArkTS 侧解析 stdout 看到哨兵后,把提示符显示到 Shell 输入框上方、置 awaitingStdin 状态;用户下一次在输入框敲的内容不再当语句执行,而是收进 stdinAccum 并带着全部 stdin 重新跑一遍脚本。
这里有一个差点造成「点击运行即闪退」的致命细节:_NeedInput 必须继承普通 Exception,绝不能用 SystemExit。因为 C++ 包装层的 PyRun_SimpleString 只能兜住 except Exception,而 SystemExit 会逃逸到 CPython 的 handle_system_exit → Py_Exit → exit()——直接把宿主 App 进程杀掉,且不留任何可见报错。同样的道理,Shell 里用户敲 exit()/quit(),我们用自定义 _ScriptExit 异常兜住,stdout 里输出 __SCRIPT_EXIT__ 哨兵,ArkTS 侧抑制其 traceback 并温和地结束本次脚本。这段防御逻辑的完整注释就写在 Index.ets 里:
/**
* 嵌入式解释器没有交互 stdin,input() 会 EOFError。做法:运行前把已收集的
* stdin 行注入 __stdin_lines__ 并覆盖 builtins.input —— 有剩余行就弹出返回;
* 没有时打印 __NEED_INPUT__:<prompt> 哨兵并 raise _NeedInput(普通 Exception,
* ⚠️ 绝不能用 SystemExit:PyRun_SimpleString 里逃逸出包装层 except Exception
* 的 SystemExit 会走 handle_system_exit → Py_Exit → exit() 直接杀掉整个进程,
* 表现为点击运行即闪退)。
*/
%Run 的仪式感。 上游 Thonny 执行当前文件时会在 Shell 打一行 %Run 文件名。我们保留了这个 magic 命令的展示(超链接蓝),它既是情怀,也帮助从桌面版 Thonny 过来的用户建立「我在跑的是哪个文件」的心智模型。
5.4 变量面板:用 JSON 协议从解释器里「偷」全局变量

右边这张截图里,变量面板列出了 fib_recursive、fib_iterative、fib_generator、lru_cache、n、unique、thonny、ver 等变量及其 repr 值。实现方式很轻:每次执行结束后,往解释器塞一段固定的内省代码——
import json as _json
_v = {}
for _k, _val in list(globals().items()):
if _k.startswith('_'):
continue
try:
_v[_k] = repr(_val)
except Exception:
_v[_k] = '<unrepr>'
print('VARS_JSON:' + _json.dumps(_v))
stdout 里以 VARS_JSON: 开头的那一行就是完整的变量快照,ArkTS 侧 JSON.parse 后渲染成表格。有两个工程细节:
- 过滤注入物。解释器初始化和我们的包装层会往全局命名空间塞东西(site-packages 路径注入用的
p、异常路径 import 的traceback、stdin 覆盖的input/exit/quit),这些不是用户变量,全部过滤; - repr 失败兜底。某些对象的
repr会抛异常(比如重写了坏__repr__的类),用<unrepr>占位,绝不能让变量面板把执行流搞挂。
截图中还能看到三个标签页:素数.py、波那契数列.py、untitled.py,以及 Shell 里同一份斐波那契代码分别以递归(@lru_cache)、迭代、生成器三种方式跑出 前 10 项 的对比输出——这正是教学场景里最常见的用法。
5.5 文件系统:全程 DocumentViewPicker,零权限
打开、保存、另存为全部走 DocumentViewPicker。这个选择有两个理由:
- 零权限。picker 是用户授权制——用户在系统文件选择器里选中哪个文件、保存到哪个目录,应用才拿到对应 URI 的临时读写权,
module.json5的requestPermissions可以保持空数组。对一个教学工具来说,「要的权限越少,家长和老师越放心」。 - 编码兜底。读取时先看 BOM,有 UTF-8 BOM 就剥掉再解码;无 BOM 的内容先按 UTF-8 解,一旦发现替换符 U+FFFD 再回退 GB18030 重解——国内初学者的
.py文件里有相当比例是老编辑器存出的 GBK 编码,这个兜底能省掉大量「打开全是乱码」的客服问题。
保存时用 DocumentSaveOptions.newFileNames 预填当前文件名,保存后标签页名跟随用户在对话框里实际起的名字,避免出现「文件已经叫 foo.py 了,标签还挂着 untitled.py」的割裂感。
5.6 关于对话框与启动策略
关于面板分层排版:官方 logo、名称、副标题、题句「一条青蟒,游于鸿蒙;一行代码,万物生长」、致谢 From Thonny、包名与构建号。上游归属必须在产品里可见——Thonny 由爱沙尼亚塔尔图大学维护(MIT),CPython 归 PSF(PSF License),我们在 README 和关于面板里都做了明确标注。
另外一个小改动体现产品思路:V1.0 启动时预置了示例代码方便验收,上架版启动即空白 untitled.py——一个上架的应用不应该替用户决定第一屏写什么。
六、运行时准备:marker 协议与「复用」判定
UI 之外,上架版还沉淀了一套可靠的运行时部署逻辑。rawfile 里的 ohos_python_stdlib.zip(stdlib + Thonny site-packages)在首次启动时解压到 filesDir/ohos_python/,但「首次」的判定不能靠感觉,我们用了一个 marker 协议:
const marker = `${this.pythonHome}/.${RUNTIME_MARKER}`; // .READY_P2_1_0_3
const reusable =
pathOpenable(osPy) && // lib/python3.12/os.py 必须真实可打开
pathOpenable(thVer) && // site-packages/thonny/VERSION 必须存在
pathExists(libDir) &&
pathExists(marker) &&
readTextFile(marker).indexOf(RUNTIME_MARKER) >= 0;
四重校验:目录在、关键文件在且可打开(不只是 stat 到)、marker 文件在、marker 内容匹配当前版本。任何运行时内容变更都要 bump RUNTIME_MARKER,老 marker 在下次启动时被 invalidateOldRuntime() 主动删除,触发完整重解压。这套机制防的是「zip 布局变了但沙箱里还留着上版本的残树」——V1.0 时代我们吃过亏:os.py 解压出来是空文件,解释器启动后 import os 直接失败,而表面上一切「看起来都在」。所以校验必须用「真实打开读一下」而不是「文件存在」,这就是 pathOpenable 和 pathExists 两个函数并存的原因。
解压完成后调 setPythonHome(pythonHome),C++ 侧会做一次同源的探测(home_dir / lib_dir / os.py / thonny 四项 + 目录抽样列举)并把结果返回给 ArkTS,任何一项异常都会在界面上弹出红色通知条而不是静默失败。最后跑一句 import sys; print(sys.version) 把 Python 版本横幅打到 Shell 顶部——等价于桌面版 Thonny Shell 的第一行。
七、构建与复现:两条路线
为了让任何人都能复现,仓库里备了两条构建路线:
路线 B(默认推荐,免交叉编译):预编译产物 prebuilt/libpython3.12.a(33MB)和 python_inc/(214 个头文件)直接入库,clone 后即可构建 HAP,不碰交叉编译。
路线 A(可选,自己编 CPython):native/build_cpython_ohos.sh 一键跑通。要点是先编 host python 3.12,再 configure --host=aarch64-unknown-linux-musl(config.sub 认 musl,OHOS 的 libc 与 musl 行为一致),实际目标由 OHOS clang 的 --target=aarch64-unknown-linux-ohos 加 sysroot 决定。脚本里处理了三个坑:构建目录放 /tmp 防沙箱写入问题、OHOS_PLATFORM_TRIPLET_PATCH 修正 multiarch 三元组、ac_cv_header_libintl_h=no 防假 libintl.h 污染 _localemodule。
Windows 机器上一条命令出包(工程根的 build_hap_tmp.cmd):
cd /d ...\ohos_Thonny_native\ohos_hap
set "DEVECO_SDK_HOME=C:\Program Files\Huawei\DevEco Studio\sdk"
set "PATH=C:\Program Files\Huawei\DevEco Studio\jbr\bin;%PATH%"
call ...\hvigorw.bat --mode module -p module=entry@default -p product=default assembleHap --no-daemon
(jbr 的 java 要前置到 PATH,因为 PackageHap 打包工具是 jar,spawn 不到 java 会报 00308018。)
从克隆到真机跑通的完整流程,我们画成了一张五步图:

- 环境确认:设备 HarmonyOS ≥ 7.0.0(26.0.0)(最低 6.1.0(135) 也能跑,但建议 7.0+),DevEco Studio + SDK 6.0.1(21)/6.0.2(22),
local.properties指向 DevEco 自带 SDK; - 克隆代码:仓库已含全部大文件(33MB 静态库 + 1.7MB 头文件 + 11MB 运行时 zip),无需额外下载;
- DevEco 打开工程:运行配置类型必须选 OhosDebugTask(不是 OhosRunConfiguration),首次打开触发自动签名,
signingConfig从空改为default; - 编译 + Run:第一次 Run 自动签名+安装+启动,真机点 Run Test 看到 P2 SUCCESS,切到执行器页即可写代码;
- 后续扩展:P3 极薄宿主与 P4 Tcl/Tk 显示层仍在路线图上(见第九节)。
签名环节的三个坑,每个都值得单独记一笔:
| 坑 | 现象 | 对策 |
|---|---|---|
signingConfig 留空 | 产物是 entry-default-unsigned.hap,安装报 9568320 | products[].signingConfig 必须填 signingConfigs 里的 name |
| SignHap 00303074 | profile 绑定了别的 bundleName | 签名材料按包名绑定,每个项目重新自动签名,p7b 不可跨项目复用 |
| 资源缓存 | 改了图标/资源,构建出来还是旧的 | 先 hvigorw --stop-daemon 再删 .hvigor 和 entry/build;只删 intermediates 子目录会报 00308018 |
八、开发环境与真机验收
日常开发在 DevEco Studio 里完成,ArkTS 层与 C++ 层同工程管理:

上架版在真机上完成回归:MateBook Pro(HarmonyOS 7.0.0(26.0.0))安装启动,运行时日志出现 [runtime] reuse existing /data/storage/el2/base/haps/entry/files/ohos_python(沙箱内解压产物正确复用),Python 横幅打出 3.12.9,随后 Shell 进入 >>> 交互态。截图中 %Run 执行去重函数、变量面板展示 lru_cache 递归/迭代/生成器三种斐波那契实现,均一次通过。
需要强调的是系统版本敏感性:我们在旧版本系统(6.x)上遇到过「进程活着但 EntryAbility 不实例化」的诡异现象——无崩溃栈、无 onCreate 日志、filesDir 为空,排查了很久最后定位是设备系统版本问题,升级 HarmonyOS 7.0.0(26.0.0) 后全部恢复。所以 README 里明确建议先升级系统,并把这条排查路径(日志埋点 → 进程状态 → so 依赖 → 环境变量)写进了故障排查章节。
九、诚实的边界:已知限制与路线图
一个上架的产品,把限制说清楚和把功能做好同样重要。当前版本的限制有三条:
- C 扩展模块不可用。静态链接的 libpython 无法 dlopen
math.so等lib-dynload扩展,纯 Python 路径 100% 可用,但依赖 C 扩展的库(numpy、requests 的 ssl 层等)装不上也跑不了。根本解法是改用 shared libpython 或把常用扩展做成内建模块,这是后续大版本的事; - 无 Tk 显示层。Tk 的传统后端是 X11/Win32/Aqua,鸿蒙没有原生 Tk 后端。所以上架版的 GUI 是 ArkUI 自绘的外壳,解释器本体 headless 运行;
- Thonny 教学功能的深度整合还在路上。当前 Shell/编辑器/变量面板覆盖了初学者的核心闭环,但上游的调试器(Thonny 最有特色的功能)、包管理器等还没有接出 UI。
路线图上还有两站:P3 极薄宿主(把 python -m thonny 的完整生命周期管理做进壳里)和 P4 显示层(评估 X11 兼容层或 Tcl/Tk 移植的可行性)。这两项涉及较大的技术选型,我们倾向先把当前 ArkUI 原生外壳这条已经验证的路走深——它性能可控、和系统集成最好,而且不受 Tk 后端缺失的制约。
十、结语
回看整个项目,最有价值的经验其实一句话就能说完:先证明真的,再做好看的。V1.0 用一个朴素的验收页锁死了「交叉编译 CPython + 上游 Thonny 源码在鸿蒙真机可跑」这个事实,之后所有的 UI 投入都建立在这个不可动摇的地基上;而 UI 升级版的每一处样式改动——clam 主题色、default_light 语法配色、%Run 仪式感、Shell 提示符——都能在上游源码里找到出处,这让「移植」二字名副其实。
对想在鸿蒙上做开源软件移植的开发者,我们的三条建议:
- noexec 沙箱是第一约束,先想清楚解释器/引擎以什么形态进进程(静态库链入是通用解),再动手写任何界面;
- 验收标准要落在内容上,exit code 和「没抛异常」说明不了任何问题,约定标记 + 逐项断言才能防住「假成功」;
- 限制要诚实,裁了什么、跑不了什么,写进文档比藏在 FAQ 里体面得多,也省下大量售后解释成本。
青蟒已在 HarmonyOS PC 应用市场上架,安装即用。源码、构建文档与踩坑清单随仓库开放,欢迎鸿蒙生态的开发者照着复现、提 issue、交 PR——毕竟,一条青蟒能游多远,取决于水有多宽。
上游归属与许可:Thonny © University of Tartu(MIT);CPython © Python Software Foundation(PSF License)。本项目为上游开源软件的 HarmonyOS 平台适配移植,分发时保留上游 LICENSE 与 NOTICE。
更多推荐





所有评论(0)