English version

前言

Snap! 的 JavaScript function 简单, 灵活且强大, 我想模仿它构建 Python function。

我们之前在这篇文章里, 基于 CodeLab Adapter 构建了一个 Python function, 但我一直更想要一个除了浏览器之外没有任何依赖的 Python function。

使用场景

有了 Python function, 我们就可以在 Snap! 中获得:

  • Python 生态中海量的第三方库(opencv-python, numpy…)
  • Python 社区已经实现的各种算法(如 TheAlgorithms)

我们既可以在 Snap! 环境中获得 Python 生态积累的丰富工具, 又可以借助 Snap! 提升 Python 的活性(liveness)。

选择 Python 解释器

多个 Python 解释器可运行在浏览器环境。 pyodide 是其中最强大的一个, 它允许我们使用 Python 生态大量的库, 如: opencv-python, Pillow, pyparsing, requests(最新的版本有问题), retrying, scikit-image 以及大量的科学计算相关的库(scipy, sympy, numpy, pandas)

实际上, pyodide 支持 任何纯 Python 实现的库(构建为 wheel), 使用了 C/C++ 代码的库(如 numpy)编译为 WebAssembly 也能够在 pyodide 中使用。

我们打算基于 pyodide 构建 Python function。

将 pyodide 引入 Snap!

目标

与我们上篇文章引入 mediapipe 库一样, 我们希望实现以下 2 个目标:

  1. 无需更新平台, 不需要开发者介入, 所有工作都在用户环境中完成(只是一个 Snap! 项目), 这意味着普通用户可以继续延伸这些能力。 (这是终端用户编程的一个例子)
  1. 可以充分利用 Snap! 的活性(liveness), 享受高效而愉快的开发体验。

由于一切都只是一个 Snap! 项目, 你可以随意调整它, 甚至使用其他的 Python 解释器替代 pyodide。

导入 pyodide

与 mediapipe 不同, pyodide 不是以模块的形式发布, 而是采用了更传统的全局引入。 我们可以使用 src_load(url) 引入它。

我们使用的 pyodide 版本为 0.25.1, 它所对应的 Python 版本为 3.11

我们没有采用官方的 CDN, 而是将 pyodide 部署到了国内的 CDN 上。

demo

使用 requests 与网络交互

Snap! 有一个积木用于与网络交互, 但使用起来不是很便利, requests 的 API 设计得非常简单易用:

requests demo

解析 XML

Snap! 没有内置 XML 库, Python 内置的 XML 库很好用,我们可以使用它从博客 rss 中提取信息。

这个例子展示了从博客获取最新 rss, 使用 python 的 xml 库解析它, 并将解析结果存入 posts 变量。之后将每篇博文显示为一个克隆体:

  • 克隆体的外观是文章标题
  • 鼠标在克隆体上悬停时,展示文章摘要
  • 点击克隆体将打开文章链接

XML demo

正则表达式

Snap! 缺少正则表达式相关的支持, 我们可以使用 python 的正则表达式模块增强它

在 Python 与 Snap! 之间传递列表

这个例子展示了如何在 Python 与 Snap! 之间传递列表(任意嵌套深度!)

列表几乎可以承载一切的数据结构.Python 和 Snap! 的列表功能都极其强大(Snap! 由于是 lisp, 列表处理能力比 python 更强大),现在这二者可以无缝互操作了。

图像、音频等多媒体数据全都可以表示为列表/矩阵,所有这些数据都能够在两个系统中随意流动了

一个典型的用例是, numpy 的所有能力都可以进入 Snap! 中, 反过来 Snap! 又可以为 numpy 提供 liveness 的增强

传递列表 demo

在 Python 与 Snap! 之间传递 json

对于复杂的数据结构, 可以考虑使用 json 传递. 也可以使用 json 来传递列表 (将列表作为一个 key 的 value ).

传递 json demo

numpy demo

numpy demo

OpenCV demo

OpenCV demo

Pillow demo

Pillow demo

社区第三方库

前边说到:

pyodide 支持 任何纯 Python 实现的库(构建为 wheel)

我们试着安装 furl 库, furl 用于方便地处理 url

pyodide 将从 pypi 上安装这个库(你也可以从指定的 url 安装自定义库)。

furl demo

FAQ

如何保存状态

函数通常是无状态的, Python function 是一个函数。

如果你期望在多个函数调用中, 操作同一个状态变量, 可以把它存放在 window 全局变量里:

1
2
3
import js
js.window._result = "hello"
# 可在 devtool 中访问: console.log(window.a)

当然你也可以把它持久化存储在 localstorage 或 indexeddb, 这样即使刷新浏览器, 也还能找到这些状态。

如何调用蓝牙, 串口等浏览器 API

这个 demo(点击体验) 展示了如何使用 Python 连接 MicroBlocks 的蓝牙服务。

第三方库从哪儿加载?

load package 积木用于从网络加载第三方库, 它在背后使用了 micropip

可以从多种来源加载 python 包:

  • pyodide 官方默认编译了这些 Python 库, 如果你加载这些库, 将默认从 pyodide 仓库里加载(我已经把它们放在了国内 CDN 上), 所以加载速度应该很快
  • 其他的库默认从 pypi 安装

你也可以直接从 url 中加载库(你可以把它们托管在自己的服务器上), 如: https://wwj718.github.io/post/img/furl-2.1.3-py2.py3-none-any.whl

如何在 Snap! 和 Python 之间传递图像文件?

跨语言/系统传递媒体类信息(音频、图片)的常见做法是使用 base64 格式, 本文的例子采用的也是这种做法。

这个对话 中, ChatGPT 解释了这些这个格式编码的 png 格式。

你可以直接将 base64 编码的字符串(形如: data:image/png;base64,iVBORxxxx...)粘贴到 Chrome 浏览器的输入栏中浏览图片:

如何将积木编译为 Python 代码?

通常而言,这不是个好想法, Snap! 的 lisp code 要更为强大(因为它与积木是同构的)。 如果确实需要编译为 Python,可以参考这个项目

如何使用更专业的 Python 编辑器?

在 Snap! 积木中写 Python 代码无法使用自动补全等功能, 如何使用更专业的 Python 编辑器?

一个思路是使用 iframe 库 引入 pyscrit(pyscrit-editor) 或者 marimo 这样更专业的 Python 编辑器, 然后使用 dynatalk-over-postmessage 进行消息传递

如何将其转化为标准的 Snap! 库?

目前 Python function 已成为了 Snap! 中文版 内置库.

更多细节参考 如何将其转化为标准的 Snap! 库?

结论

本文构建了与 JavaScript function 风格相同的 Python function 积木,一切都运行在浏览器中,没有任何外部依赖。 所有工作都在积木环境中完成,没有修改一行 Snap! 源代码。

最令人满意的是所有这些扩展性都掌握在普通用户自己的手里,无需开发人员的介入,也无需更新平台。一切都只是一个普通的 Snap! 项目!

参考