Pyodide 独立项目化和 0.17 版本发布

我们很高兴地宣布 Pyodide 已成为一个独立的社区驱动项目。我们也高兴地宣布 Pyodide 的 0.17 版本发布,它包含许多新功能和改进。

Pyodide 由编译到 WebAssembly 的 CPython 3.8 解释器组成,它允许 Python 在浏览器中运行。许多流行的科学 Python 包也已编译并提供。此外,Pyodide 可以从 Python 包索引 (PyPi) 安装任何具有纯 Python 轮子的 Python 包。Pyodide 还包含一个全面的外部函数接口,它将 Python 包生态系统暴露给 Javascript 和浏览器用户界面(包括 DOM)到 Python。

你可以在 REPL 中直接在你的浏览器中试用最新版本的 Pyodide。

Pyodide 现在是一个独立的项目

我们很高兴地宣布 Pyodide 现在在单独的 GitHub 组织 (github.com/pyodide) 中拥有一个新家,并由志愿者贡献者团队维护。该项目文档可在 pyodide.org 上获得。

Pyodide 最初是在 Mozilla 内部开发的,目的是允许在 Iodide 中使用 Python,这是一项实验性工作,旨在为网络构建一个交互式科学计算环境。自 其最初发布和宣布 以来,Pyodide 吸引了社区的极大兴趣,一直处于积极开发中,并且在 Mozilla 之外的许多项目中使用。

核心团队已批准了一份透明的 治理文档 以及一份 未来发展的路线图。Pyodide 还具有一个 行为准则,我们希望所有贡献者和核心成员都遵守该准则。

欢迎新贡献者参与 Github 项目开发。贡献的方式有很多,包括代码贡献、文档改进、添加包以及将 Pyodide 用于你的应用程序并提供反馈。

Pyodide 0.17 版本

Pyodide 0.17.0 是对先前版本的一个重大进步。它包括

  • 主要的维护改进,
  • 对核心 API 的彻底重新设计,以及
  • 仔细消除错误泄漏和内存泄漏

类型转换改进

类型转换模块在 v0.17 中进行了重大重构,目标是 Python 和 Javascript 之间的对象往返转换会生成相同的对象。

换句话说,Python -> JS -> Python 转换和 JS -> Python -> JS 转换现在生成的对象与原始对象相等。(由于不可避免的设计权衡,这里有一些例外情况。)

Pyodide 的优势之一是 Python 和 Javascript 之间的外部函数接口,它在最佳情况下可以实际消除使用两种不同语言带来的心理负担。所有 I/O 必须通过通常的网络 API 传递,因此,为了使 Python 代码能够利用浏览器的优势,我们需要能够支持用例,例如在 Python 中生成图像数据并将数据渲染到 HTML5 Canvas 中,或在 Python 中实现事件处理程序。

在过去,我们发现使用 Pyodide 的主要痛点之一是,当对象从 Python 往返 Javascript 并返回 Python 后,它会变得不同。这违反了用户的预期,并强迫使用不优雅的解决方法。

往返转换问题主要是由 Python 类型隐式转换为 Javascript 引起的。隐式转换旨在提供便利,但该系统对用户来说不够灵活且令人惊讶。我们仍然隐式转换字符串、数字、布尔值和 None。大多数其他对象在语言之间使用代理共享,这些代理允许从另一种语言调用对象的方法和一些操作。代理可以使用新的显式转换方法(称为 .toJsto_py)转换为本机类型。

例如,给定 JavaScript 中的一个数组,

window.x = ["a", "b", "c"];

我们可以在 Python 中访问它,

>>> from js import x # import x from global Javascript scope
>>> type(x)
<class 'JsProxy'>
>>> x[0]    # can index x directly
'a'
>>> x[1] = 'c' # modify x
>>> x.to_py()   # convert x to a Python list
['a', 'c']

为更复杂的使用案例添加了其他几个转换方法。这为用户提供了比以前更精细的类型转换控制。

例如,假设我们有一个 Python 列表,并且想将其用作 Javascript 函数的参数,该函数期望一个数组。调用者或被调用者需要处理转换。这允许我们直接调用不知道 Pyodide 的函数。

以下是在 Python 中调用 Javascript 函数并在 Python 侧进行参数转换的示例


function jsfunc(array) {
  array.push(2);
  return array.length;
}

pyodide.runPython(`
from js import jsfunc
from pyodide import to_js

def pyfunc():
  mylist = [1,2,3]
  jslist = to_js(mylist)
  return jsfunc(jslist) # returns 4
`)

如果 jsfunc 是一个 Javascript 内置函数,而 pyfunc 是我们代码库的一部分,这将很有效。如果 pyfunc 是一个 Python 包的一部分,我们可以在 Javascript 中处理转换。


function jsfunc(pylist) {
  let array = pylist.toJs();
  array.push(2);
  return array.length;
}

有关更多信息,请参见 类型转换文档

Asyncio 支持

另一个主要的新功能是实现了 Python 事件循环,该循环将协程调度到浏览器事件循环中运行。这使得在 Pyodide 中使用 asyncio 成为可能。

此外,现在可以在 Python 中等待 Javascript Promise,并在 Javascript 中等待 Python 可等待对象。这允许 Python 中的 asyncio 与 Javascript 无缝互操作(尽管在复杂的使用案例中可能会出现内存管理问题)。

以下是一个示例,其中我们定义了一个 Python 异步函数,该函数等待 Javascript 异步函数“fetch”,然后我们在 Javascript 中等待 Python 异步函数。


pyodide.runPython(`
async def test():
    from js import fetch
    # Fetch the Pyodide packages list
    r = await fetch("packages.json")
    data = await r.json()
    # return all available packages
    return data.dependencies.object_keys()
`);

let test = pyodide.globals.get("test");

// we can await the test() coroutine from Javascript
result = await test();
console.log(result);
// logs ["asciitree", "parso", "scikit-learn", ...]

错误处理

现在可以在 Python 中抛出错误,并在 Javascript 中捕获,或在 Javascript 中抛出错误,并在 Python 中捕获。对它的支持是在最低级别集成的,因此 Javascript 和 C 函数之间的调用按预期执行。错误转换代码由 C 宏生成,这使得实现和调试新的逻辑变得非常简单。

例如


function jserror() {
  throw new Error("ooops!");
}

pyodide.runPython(`
from js import jserror
from pyodide import JsException

try:
  jserror()
except JsException as e:
  print(str(e)) # prints "TypeError: ooops!"
`);

Emscripten 更新

Pyodide 使用 Emscripten 编译器工具链将 CPython 3.8 解释器和具有 C 扩展的 Python 包编译到 WebAssembly。在这个版本中,我们终于完成了迁移到最新版本的 Emscripten,该版本使用上游 LLVM 后端。这允许我们利用工具链的最新改进,包括显著减少包大小和执行时间。

例如,SciPy 包从 92 MB 缩减到 15 MB,因此 Scipy 现在被浏览器缓存。这极大地提高了依赖于 scipy 的科学 Python 包(如 scikit-image 和 scikit-learn)的可用性。仅包含 CPython 标准库的 Pyodide 基本环境的大小从 8.1 MB 缩减到 6.4 MB。

在性能方面,最新的工具链带来了 25% 到 30% 的运行时间改进

性能介于接近原生到慢 3 到 5 倍之间,具体取决于基准。上述基准是在 Firefox 87 中创建的。

其他更改

其他值得注意的功能包括

  • 修复了 Safari v14+ 和其他基于 Webkit 的浏览器中的包加载
  • 在 micropip 和 loadPackage 中添加了对相对 URL 的支持,并改进了 micropip 和 loadPackage 之间的交互
  • 支持在 Javascript 中实现 Python 模块

我们还进行了一些大量的维护工作和代码质量改进

  • 修复了许多错误
  • 将许多补丁上游到 emscripten 编译器工具链
  • 向 C 代码添加了系统化的错误处理,包括 Javascript 错误和 CPython 错误之间的自动适配器
  • 添加了内部一致性检查以检测内存泄漏、检测致命错误并提高调试的简便性

有关更多详细信息,请参见 更改日志

Iodide 逐步收尾

Mozilla 做出了一个艰难的决定,要逐步收尾 Iodide 项目。虽然 alpha.iodide.io 目前仍将继续提供(部分是为了展示 Pyodide 的功能),但我们不建议将其用于重要的工作,因为它可能会在将来关闭。自 Iodide 发布以来,出现了 许多基于 Pyodide 创建交互式笔记本环境的努力,这些努力正处于积极开发中,并提供了一个类似的环境,用于使用 python 在浏览器中创建交互式可视化。

Pyodide 的下一步

虽然在这个版本中解决了许多问题,但路线图上仍有许多其他重大步骤。我们可以提一下

  • 减少下载大小和初始化时间
  • 提高 Pyodide 中 Python 代码的性能
  • 简化包加载系统
  • 将 scipy 更新到更近的版本
  • 更好地支持项目可持续性,例如,通过寻求与 conda-forge 项目及其工具的协同作用。
  • 更好地支持 Web 工作线程
  • 更好地支持同步 IO(在编程教育中很受欢迎)

有关更多信息,请参见 项目路线图

鸣谢

衷心感谢

我们也感谢所有 Pyodide 贡献者

关于 Roman Yurchak

数据科学家和 Symerio 创始人

更多 Roman Yurchak 的文章...

关于 Hood Chatham

UCLA 的 NSF 博士后,从事代数拓扑研究

更多 Hood Chatham 的文章...

关于 Teon Brooks

公共利益技术专家。Mozilla 产品经理。计算教育实验室执行主任。

更多 Teon Brooks 的文章…

关于 Will Lachance

Mozilla 数据工程师

更多 Will Lachance 的文章…