news 2026/9/12 18:25:38

把 Jupyter Notebook 搬到鸿蒙 PC:同栈双胞胎的 Notebook-first 移植实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
把 Jupyter Notebook 搬到鸿蒙 PC:同栈双胞胎的 Notebook-first 移植实战

把 Jupyter Notebook 搬到鸿蒙 PC:同栈双胞胎的 Notebook-first 移植实战

欢迎加入开源鸿蒙 PC 社区:https://harmonypc.csdn.net/

欢迎在 PC 社区平台申请新建项目:https://atomgit.com/OpenHarmonyPCDeveloper

适配开源地址:https://atomgit.com/OpenHarmonyPCDeveloper/ohos_jupyterNotebook

写在前面

这是最有意思的一篇——因为它不是从零开始移植一个应用,而是从已经跑通的姊妹工程里分叉出另一个产品形态。

Jupyter 生态里有个特殊现象:Notebook 7 的前端就是 JupyterLab 组件构建的。上游jupyter/notebook从 7.0 开始放弃了老nbclassic的 jQuery 代码库,改为复用 Lab 的 React/Phosphor 前端。这意味着:如果你已经把 JupyterLab 移植到了鸿蒙 PC,你就已经顺手完成了 Jupyter Notebook 90% 的移植工作——剩下的是产品身份分叉和 UI 形态收敛。

任务清单要求两个产品分列交付、独立安装、独立评分,所以这次的核心问题不是「能不能跑」(Lab 已经验证),而是「怎么把一套已验证的技术栈,切成两个能共存、可独立演进的交付物」。



一、为什么 Notebook 7 是「最容易的硬骨头」

先看清楚上游关系:

jupyter/notebook (Notebook 7) │ 前端复用 ▼ jupyterlab/jupyterlab (Lab 组件库) │ OHOS 适配已验证(姊妹工程 ohos_JupyterLab) ▼ OpenHarmony Electron (libelectron.so) + node-static + 浏览器内 Python

Notebook 7 = Lab 前端 + single-document 外壳。上游用pageConfig控制产品形态:

配置JupyterLab 产品Jupyter Notebook 产品
pageConfig.modemultiple-documentsingle-document
启动 URL/lab(Launcher 多文档入口)/lab/tree/Untitled.ipynb(直接打开笔记本)
Launcher 卡片页禁用
左侧文件浏览器禁用
TOC 侧栏禁用
bundleNameorg.jupyter.lab.ohosorg.jupyter.notebook.ohos
环境变量前缀JUPYTERLAB_*NOTEBOOK_*(兼容JUPYTERLAB_*

所以移植策略非常清晰:fork 姊妹工程 → 改产品身份配置 → 保持技术栈完全复用


二、从 Lab 分叉:bootstrap 脚本做了什么

两个仓库之间用一个 bootstrap 脚本同步 runtime(约 2000 行 Electron 主进程代码不重复维护):

nodescripts/bootstrap-from-notebook.mjs# Notebook → 姊妹方向nodescripts/bootstrap-from-lab.mjs# 反向(Lab 仓库持有源)

脚本做的字符串/配置级重写:

org.jupyter.lab.ohos → org.jupyter.notebook.ohos productUi: 'lab' → productUi: 'notebook' /lab(Launcher 入口) → /lab/tree/Untitled.ipynb JUPYTERLAB_*(优先) → NOTEBOOK_*(优先,JUPYTERLAB_* 兼容) hnpPackages type: private → type: public ← 关键差异,见 §四

工程判断:这种「同栈双产品」的架构下,bootstrap 脚本就是唯一的真相源。所有产品身份相关的东西都集中在这一个脚本里,避免两个仓库手工 drift。


三、架构:和 Lab 完全同栈,只有入口不同

三种服务模式与 Lab 完全一致:

模式环境变量场景
node-static(默认)NOTEBOOK_OHOS_SERVER_MODE=node-static静态前端 + 浏览器内内核,零原生依赖
pythonNOTEBOOK_OHOS_FORCE_PYTHON=1设备上跑python -m jupyterlab(需 aarch64 发行版)
externalNOTEBOOK_EXTERNAL_SERVER_URL=…连接已有 Jupyter Server

为什么默认 node-static 而不是 python 模式?pyzmq/libsodium 在设备侧仍然脆弱(官方文档原话),交叉编译一个能跑 Jupyter Server 的 Python 环境的工程成本,远超「静态前端 + 浏览器内解释器」的方案。这是整个 Jupyter 双胞胎移植里最重要的一个工程决策。


四、HNP 双胞胎共存机制(本次最大坑)

两个产品都打包了一个 HNP 原生包(jupyterlab_python.hnp,用于可选的原生 Python 能力)。第一次装 Notebook 时直接翻车:

Install Failed: code:9568407 Failed to install the HAP because installing the native package failed.

hilog 关键线索:

ProcessBundleInstallNative … hnp install: electron [HNP API] native package install! … package name=org.jupyter.notebook.ohos already exist cfg ignore … MSG_ERR_NATIVE_INSTALL_FAILED

根因:HNP 包有public/private两种作用域:

类型作用域冲突规则
public设备全局唯一同名 public 包只能装一个
private单应用沙箱每个应用各持一份,互不干扰

两个产品声明了同名jupyterlab_python.hnp,如果都是 public,先装的占坑,后装的报 9568407。

修复方案(不对称设计):

// Notebook(先发布,占 public 坑) "hnpPackages": [ { "package": "jupyterlab_python.hnp", "type": "public" } ] // Lab(后发布,用 private 共存) "hnpPackages": [ { "package": "jupyterlab_python.hnp", "type": "private" } ]

改完必须重新assembleHap——模块元数据是烘焙进 HAP 的,热改无效。

方法论:任何「同栈双胞胎」交付,都要在 HNP 这一层显式设计共存策略,且不对称(一个 public 一个 private)是最稳的——对称 public 会互相踢,对称 private 浪费设备空间且语义不明。


五、真机验收:六个功能点逐一过

设备:HUAWEI MateBook Pro,HarmonyOS 7.0.0。

5.1 启动直达笔记本(single-document 的核心体验)

这就是 Notebook-first 与 Lab-first 的用户可感知差异:打开就是笔记本,光标在 cell 里,可以直接打代码。Click to add a cell.提示、Mode: Command/Edit状态切换、Ln x, Col y行列号全部工作。

5.2 Cell 执行与返回值显示

注意[3]: None这一行——这是 Jupyter 对表达式返回值的自动回显print()返回 None)。这个细节存在,说明 Skulpt 内核的执行协议和前端的消息通道是完整实现的,不是简单地把 stdout 打到页面上。

5.3 菜单完整度

20+ 菜单项、快捷键提示、二级菜单全部渲染正确。右下角还能看到系统任务栏(BOSS 直聘、邮件、抖音)——这是真机全屏截图,不是模拟器。

5.4 键盘快捷键帮助

弹窗、滚动、关闭交互全部正常——Lab 前端的 Modal 组件在 OHOS Electron 上没有降级。

5.5 ASCII 艺术字输出(多行字符串渲染)

值得记录的一个细节:这些 box 字符(╔═╗║╚╝)在r"""..."""raw 三引号字符串里会触发 Skulpt 词法器 bugSyntaxError: bad input),改用普通"""..."""三引号或分行print()就完全正常。截图里的输出就是修复后的结果——这是 Skulpt 内核的边界,不是平台问题

5.6 无 matplotlib 环境下的数据可视化(本文最有价值的一节)

鸿蒙 PC 的 Jupyter 双胞胎默认内核是 Skulpt,没有 matplotlib / numpy。但数据可视化需求是真实的——教学场景画个函数曲线、演示趋势,不能没有。解法是纯标准库 ASCII 折线图

完整代码(约 40 行,纯math+ 列表推导,Skulpt 100% 兼容):

importmath# 1. 数据xs=[i*0.4foriinrange(24)]ys=[math.sin(x)*3+x*0.25forxinxs]# 2. 画布W,H=64,18min_y,max_y=min(ys),max(ys)min_x,max_x=min(xs),max(xs)defto_col(x):returnint((x-min_x)/(max_x-min_x)*(W-2))+1defto_row(y):returnint((y-min_y)/(max_y-min_y)*(H-2))# 3. 画布初始化 + 坐标轴canvas=[[' 'for_inrange(W)]for_inrange(H)]forrinrange(H):canvas[r][0]='│'forcinrange(W):canvas[H-1][c]='─'canvas[H-1][0]='└'# 4. 描点 + 插值连线foriinrange(len(xs)):col=to_col(xs[i])row=H-1-to_row(ys[i])canvas[row][col]='●'ifi>0:prev_col=to_col(xs[i-1])span=col-prev_colifspan>1:forcinrange(prev_col+1,col):t=(c-prev_col)*1.0/span y=ys[i-1]+(ys[i]-ys[i-1])*t canvas[H-1-to_row(y)][c]='·'# 5. 峰谷标注peak_i=ys.index(max(ys))low_i=ys.index(min(ys))canvas[H-1-to_row(ys[peak_i])][to_col(xs[peak_i])]='▲'canvas[H-1-to_row(ys[low_i])][to_col(xs[low_i])]='▼'# 6. 输出print("y_max = {:.2f}".format(max_y))forrowincanvas:print(''.join(row))print("y_min = {:.2f} x: {:.1f} -> {:.1f}".format(min_y,min_x,max_x))

几个工程要点:

  • 刻意不用 f-string(部分 Skulpt 版本支持不稳),全部.format()
  • 刻意不用 raw stringr"""是词法坑),box 字符只出现在普通短字符串里
  • 插值连线拆成span/t/y三个中间变量,避免长链表达式触发解析器边界
  • [i]: None返回值回显、错误行号定位(bad input on line N)都正常工作

这套「降级可视化」思路适用于任何没有图形栈的嵌入式 Python 环境——Skulpt、MicroPython、受限容器都是同一个问题域。


六、双胞胎工程的坑位对照

这次 Notebook 分叉把 Lab 踩过的坑原样再踩一遍,但每个坑的解法都能直接复用,成本大幅下降:

Lab 首次解决成本Notebook 复用成本
CompileArkTS 10705000(import lazy排查半天,定位到compatibleSdkVersionStage: beta10(bootstrap 直接钉住6.0.1(21)
SignHap 00303074(profile 绑 bundle)摸清 debug profile 的 bundle 绑定机制5 分钟(DevEco Fix 一次)
Install 9568320(signingConfig: ""半小时(发现 DevEco 不回填 products 引用)5 分钟(知道看产物文件名)
Install 9568407(HNP 冲突)理解 public/private 作用域0(bootstrap 写好public
resfile 放错模块白屏半天0(脚本生成正确路径)

这就是「同栈双胞胎」模式的最大红利:第一个产品的踩坑成本是沉没成本,第二个产品的边际成本趋近于零。反过来,如果两个产品各自独立移植,这些坑要踩两遍。

七、复现命令

cdohos_JupyterNotebook# 1. 从 Lab 工程同步 runtime(首次必做)nodescripts/bootstrap-from-lab.mjs# 或按仓库实际脚本名# 2. 打包 runtime 到 web_engine resfilenodepkg/ohos/build-package.mjs# 3. 构建 HAPnodepkg/ohos/build-hap.mjs# 跳过 hvigor 仅打包资源:# NOTEBOOK_OHOS_SKIP_HVIGOR=1 node pkg/ohos/build-hap.mjs# 4. DevEco 自动签名 → 检查 products[].signingConfig = "default"# 5. 安装 + 启动hdcinstall-relectron/build/default/outputs/default/electron-default-signed.hap hdc shell aa start-aEntryAbility-borg.jupyter.notebook.ohos

环境变量速查:NOTEBOOK_PORT=8888NOTEBOOK_TOKEN(默认随机)、NOTEBOOK_ROOT_DIR=<userData>/notebooksNOTEBOOK_DISABLE_GPU=1


八、给「同栈多产品」交付的方法论

这套模式适用于所有「一个技术栈要交付多个产品形态」的场景(IDE 的社区版/专业版、浏览器的稳定版/测试版、办公套件的多个组件):

  1. 先跑通一个,再分叉第二个——第一个产品的踩坑日记就是第二个产品的施工图
  2. bootstrap 脚本是唯一真相源——产品身份的所有差异集中在一个可 review 的 diff 里,禁止手工双仓库漂移
  3. HNP/权限/签名按 bundle 隔离——debug profile 绑 bundleName,HNP 设计不对称共存(public/private),一个都别复用
  4. UI 形态差异交给上游配置——pageConfig.mode这种官方开关比魔改前端代码稳定一个数量级
  5. 降级路径先于完整路径——node-static + 浏览器内内核先落地,原生 Server 后补;能跑的 60 分比跑不起来的 100 分有价值
  6. 受限环境的可视化用 ASCII——没有图形栈时,40 行纯标准库代码就能覆盖教学场景 80% 的画图需求

回头看这个项目最大的价值不是「又移植了一个应用」,而是验证了同栈双胞胎的工程模式:Notebook 7 的分叉只花了 Lab 首次移植约 20% 的时间,而这 80% 的节省全部来自 Lab 留下的踩坑记录和 bootstrap 脚本。如果你手上也有类似「一个底座多个产品」的移植任务,强烈建议按这个模式组织仓库——第一个产品慢一点没关系,它是在为后面的所有产品铺路。


常见问题 FAQ

Q1:和 JupyterLab 版有什么区别?能同时装吗?

能共存。两者技术栈完全相同,差异只在产品形态:Notebook 打开就是单个笔记本(single-document,无 Launcher / 文件浏览器),Lab 是多文档 + Launcher。HNP 层做了不对称设计(Notebook 用public、Lab 用private),互不冲突。

Q2:为什么打开后不是经典的/tree老界面?

上游 Notebook 7 已经放弃老nbclassic的 jQuery 前端,改为复用 Lab 组件。本工程走的是官方路线——Lab 静态资源 +pageConfig.mode = single-document,视觉上是「单文档笔记本」,但底层不是老/tree

Q3:能装 numpy / matplotlib 吗?

默认不能。内核是 Skulpt(浏览器内 Python 子集),没有 C 扩展生态。两个出路:切NOTEBOOK_EXTERNAL_SERVER_URL连远程 Jupyter Server 用真内核;或者用纯标准库 ASCII 图表顶住教学场景(见 §5.6 的 40 行折线图代码)。

Q4:为什么r"""..."""三引号字符串报SyntaxError: bad input

Skulpt 词法器对 raw 三引号 + box-drawing 多字节字符有边界 bug。去掉r前缀用普通"""...""",或把内容拆成多行print()就正常。真 Python 解释器跑同样代码没问题——这是内核限制,不是平台 bug。

Q5:签名成功了还是报9568320 no signature file

看产物文件名。unsigned说明build-profile.json5products[].signingConfig还是空串——DevEco 自动签名只写材料不回填这个引用。手动改成"default"重新构建。

Q6:装了 Lab 再装 Notebook,报9568407原生包失败?

HNP 同名冲突。两个产品的hnpPackages里声明了同名jupyterlab_python.hnppublic类型设备全局唯一,后装的会被拒。用新版 HAP(Notebookpublic/ Labprivate的不对称组合)即可共存;不想重编就先卸载另一个再装。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/12 18:25:35

ARM Cortex-M边缘AI唤醒模型源码深度解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/12 18:21:25

使用 Repomix 与 GitHub Actions 自动化打包代码库:完整实战指南

使用 Repomix 与 GitHub Actions 自动化打包代码库&#xff1a;完整实战指南 【免费下载链接】repomix &#x1f4e6; Repomix is a powerful tool that packs your entire repository into a single, AI-friendly file. Perfect for when you need to feed your codebase to L…

作者头像 李华
网站建设 2026/9/12 18:19:34

Trae项目架构升级:AI自动化编程工具链的三大突破

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/12 18:18:39

windows 驱动实例分析系列: HidHide驱动分析-HidHideCLI 篇(四)

HidHide 驱动分析 - HidHideCLI 篇&#xff08;四&#xff09;&#xff1a;日志系统与异常处理 一、日志系统的分层架构 HidHideCLI 的日志系统与内核驱动共享相同的 ETW 提供者结构&#xff0c;但在用户态实现了独立的日志后端。日志系统分为三个层次&#xff1a; 1.1 宏定义层…

作者头像 李华
网站建设 2026/9/12 18:16:41

OPA衍生化原理详解:从氨基酸分析到蛋白定量的高灵敏度荧光方案

做蛋白定量&#xff0c;实验室里第一反应多半是BCA或者考马斯亮蓝&#xff1b;做氨基酸分析&#xff0c;很多老方法会用到茚三酮。但有一类场景&#xff0c;这两套常用方案都不太顺手——样品浓度低到微克级以下、样品是多肽水解物、又或者你需要把二十种氨基酸一次性搞定。这时…

作者头像 李华