将Python编译为WebAssembly

128

我读到过 Python 2.7 代码可以转换为 Web Assembly,但是我找不到关于如何进行的明确指南。

目前为止,我已经使用 Emscripten 编译了一个 C 程序到 Web Assembly 并且所有必要的组件都在运行中(使用的指南: http://webassembly.org/getting-started/developers-guide/)。

在 Ubuntu 机器上,我需要采取哪些步骤才能做到这一点?我是否需要将 Python 代码转换为 LLVM bitcode,然后使用 Emscripten 进行编译?如果是这样,我该如何实现?


2
@guettli https://github.com/pypyjs/pypyjs/issues/145 - denfromufa
3
请查看 pyodide:https://hacks.mozilla.org/2019/04/pyodide-bringing-the-scientific-python-stack-to-the-browser - Alex
3
Pyodide通过WebAssembly将Python运行时带到浏览器中:https://github.com/iodide-project/pyodide - guettli
5个回答

190

WebAssembly vs asm.js

首先,让我们看一下原则上WebAssembly与asm.js有何不同,以及是否有可能重用现有的知识和工具。以下内容提供了很好的概述:

让我们回顾一下,WebAssembly(MVP,因为在其路线图中还有更多):

  • WebAssembly是一种具有静态类型的AST二进制格式,可以在现有JavaScript引擎上执行(因此可以JIT编译或AOT编译),
  • 它比JavaScript更紧凑(gzip压缩比较)快10-20倍,解析速度高一个数量级,
  • 它可以表达更低级别的操作,这些操作无法适应JavaScript语法,例如asm.js(例如64位整数、特殊CPU指令、SIMD等)
  • 可以(在某种程度上)转换为/from asm.js。

因此,目前WebAssembly是对asm.js的迭代,仅针对C/C++(和类似语言)。

Python在Web上的应用

我看起来似乎不是唯一阻止Python代码针对WebAssembly/asm.js的因素。这两者都代表了低级别的静态类型代码,其中Python代码无法(现实地)表示。由于WebAssembly/asm.js的当前工具链基于LLVM,可以轻松编译为LLVM IR语言,从而转换为WebAssembly/asm.js。但遗憾的是,Python也太动态了,无法适应它,正如Unladen SwallowPyPy的几次尝试所证明的那样。

这个asm.js演示文稿有关于动态语言状态的幻灯片。这意味着目前只能将整个VM(C/C++中的语言实现)编译成WebAssembly/asm.js,并解释(在可能的情况下使用JIT)原始源代码。对于Python,有几个现有项目:

  1. PyPy: PyPy.js (author's talk at PyCon). Here's release repo. Main JS file, pypyjs.vm.js, is 13 MB (2MB after gzip -6) + Python stdlib + other stuff.

  2. CPython: pyodide, EmPython, CPython-Emscripten, EmCPython, etc. empython.js is 5.8 MB (2.1 MB after gzip -6), no stdlib.

  3. Micropython: this fork.

    There was no built JS file there, so I was able to build it with trzeci/emscripten/, a ready-made Emscripten toolchain. Something like:

     git clone https://github.com/matthewelse/micropython.git
     cd micropython
     docker run --rm -it -v $(pwd):/src trzeci/emscripten bash
     apt-get update && apt-get install -y python3
     cd emscripten
     make -j
     # to run REPL: npm install && nodejs server.js 
    

    It produces micropython.js of 1.1 MB (225 KB after gzip -d). The latter is already something to consider, if you need only very compliant implementation without stdlib.

    To produce WebAssembly build you can change line 13 of the Makefile to

     CC = emcc -s RESERVED_FUNCTION_POINTERS=20 -s WASM=1
    

    Then make -j produces:

     113 KB micropython.js
     240 KB micropython.wasm
    

    You can look at HTML output of emcc hello.c -s WASM=1 -o hello.html, to see how to use these files.

    This way you can also potentially build PyPy and CPython in WebAssembly to interpret your Python application in a compliant browser.

另一个可能有趣的东西是Nuitka,它是一个Python到C++的编译器。理论上,可以将Python应用程序构建为C++,然后使用Emscripten与CPython一起编译。但实际上我不知道如何操作。

解决方案

目前,如果您正在构建传统网站或Web应用程序,其中下载几兆字节的JS文件几乎不可行,请尝试Python到JavaScript的转换器(例如Transcrypt)或JavaScript Python实现(例如Brython)。或者尝试其他编译为JavaScript的语言列表

否则,如果下载大小不是问题,并且您准备好处理许多棘手的问题,请在上述三种选择之间进行选择。

2020年第三季度更新

  1. JavaScript port 被集成到MicroPython中。它位于ports/javascript

  2. 该端口作为一个npm包MicroPython.js提供。您可以在RunKit中试用它。

  3. 有一个活跃的Python实现,使用Rust编写,叫做RustPython。由于Rust官方支持WebAssembly作为编译目标,因此毫不意外地,在自述文件的顶部就有演示链接。尽管如此,这还处于早期阶段。他们的免责声明如下:

    RustPython 处于开发阶段,不应在生产环境或容错设置中使用。

    我们当前的构建仅支持 Python 语法的子集。

2023年第一季度更新

Python 3.11在其文档中识别了两个WebAssembly“平台”,并在其中记录了其API的可用性,以及其他平台(如Linux和Unix)的详细信息(有关更多详细信息,请参见此PR)。它还推荐使用Pyodide(来自Mozilla)和另一个基于它的端口PyScript(来自Anaconda)。

The WebAssembly 平台的 wasm32-emscriptenEmscripten) 和 wasm32-wasiWASI)提供 POSIX API 的子集。 WebAssembly 运行时和浏览器都是沙盒化的,对主机和外部资源的访问受到限制。任何使用进程、线程、网络、信号或其他形式的进程间通信 (IPC) 的 Python 标准库模块都不可用或可能不能像其他类 Unix 系统上那样工作。

[...]

对于在浏览器中使用 Python,用户应该考虑使用 PyodidePyScript。 PyScript 建立在 Pyodide 之上,而 Pyodide 本身则建立在 CPython 和 Emscripten 之上。 Pyodide 提供了访问浏览器的 JavaScript 和 DOM API 的能力,以及通过 JavaScript 的 XMLHttpRequest 和 Fetch API 进行有限的网络功能。


2
那些 .js 和 .wasm 的大小并不太公平。流压缩得到了很好的支持,可以用来减小它们的大小。这些文件经过gzip压缩后会有多大?除此之外,回答得不错。 - enigmaticPhysicist
1
想要补充一下,在2020年,pyodide似乎是OP所寻找的最接近的东西。它是Web Assembly中的Python运行时(我会假设将C和Python放入wasm中)。它也支持多个库,并且看起来易于使用。 - David Frick
那么我不能使用Pyodide来创建一个带有WebAssembly的WebApp吗?我想使用Flask作为后端。 - ShifraSec
另一个用 Rust 编写的 Python 解释器有什么用处? - HaMAD
2
@HaMAD Rust的使用是因为它对编译成WASM有很好的支持,这意味着你可以潜在地将Rust版本的Python编译成WASM。你也可以将C程序编译成WASM,但我不确定让它正常工作有多么顺畅。 - Chinoto Vokro

10
简而言之:有转换器,但你不能自动将任意Python代码转换为Web Assembly,而且我怀疑在很长一段时间内也不可能做到。尽管理论上两种语言同样强大,手动翻译总是可行的,但Python允许使用一些数据结构和表达模式,需要非常智能的跨语言编译器(或转换器)[见下文]。一种解决方法可能是Python转C再转Web Assembly,因为python-to-C技术已经比较成熟,但这通常也行不通,因为Python-to-C也很脆弱(见下文)。
WebAssembly专门针对类似C的语言,如您可以在http://webassembly.org/docs/high-level-goals/中看到。
使用像PyPy这样的工具可以将Python转换为C,这个工具已经开发了很长时间,但仍然无法处理任意Python代码。原因有以下几点:
1. Python拥有一些非常方便、抽象和好用的数据结构,但它们很难转换为静态代码。 2. Python依赖于动态垃圾回收。 3. 大多数Python代码严重依赖于各种库,每个库都有其自己的怪癖和问题(例如用C甚至汇编语言编写)。
如果您仔细研究为什么Python-to-C(或Python to C++)如此棘手,您可以看到这个简洁答案背后的详细原因,但我认为这超出了您的问题范围。

3

21
并非一定如此。你可以在Wasm上实现垃圾回收,尤其是参考计数,就像Python中使用的那样。原则上,你应该能够使用Emscripten将CPython编译成Wasm。 - Andreas Rossberg
1
我从原帖中的理解是他们想要使用现有的工具 -- 在wasm上实现cpython GC听起来像是一个项目。 - Malcolm White
4
您不需要额外做任何事情,只需让CPython编译即可。据我所知,它已经包含了RC实现。 - Andreas Rossberg
这个月,Chrome和Firefox都发布了这个功能。在这里他们解释了为什么(虽然不是严格要求),但这是非常实际的未来发展方向。 - undefined

0

Python对于WASM来说过于动态,因此你只能得到在WASM中实现的解释器,而不是编译后的代码。

另一种方法是将Python作为工具语言,并将自己的模型编译器编写成WASM。我的意思不是源代码到WASM的编译,而是将应用程序的模型转换为WASM或其他低级语言,比如Rust或C++的编译器。

这样的模型可以实现为一个数据结构,由描述应用程序部分的对象组合而成:模块、类、API接口、GUI元素等。这些对象必须具有代码生成方法,它们“知道”如何将其状态和行为写入低级代码片段。

接下来,您可以通过将这些生成的代码片段组合成项目来手动构建应用程序,该项目可以使用启用了WASM的编译器或仅使用WAT文件进行构建。


0

我脑海中首先想到的是PyPy和rpython,然后我发现

https://github.com/soIu/rpython

我们无法将任意的Python代码转换为RPython。但是,如果您需要一种替代语言而不是Rust或AssemblyScript,那么RPython可能是一个方向。

因此,我可以使用Python/JS/Rust与WASM作为沙盒,并使用非常Pythonic的语言(如RPython)进行编码。至少我不需要担心整数溢出的问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接