长期以来,Python 社区一直在探讨使 Python 成为现代 Web 浏览器中一等公民的最佳方法。最大的挑战是: Web 浏览器实际上只支持一种编程语言:JavaScript。然而,随着网络技术的进步,我们已经将越来越多的应用程序推向了网络,如游戏、科学可视化以及音频和视频编辑软件。这意味着我们已经将繁重的计算带到了 Web 上——这是 JavaScript 所不为之而设计的。所有这些挑战都提出了对 Web 的低级语言的需求,该语言可以提供快速、可移植、紧凑和安全的执行。因此,主要的浏览器供应商致力于这个想法,并在 2017 年向世界推出了 WebAssembly。
在本教程中,我们将了解 WebAssembly 如何帮助您在浏览器中运行 Python 代码。
什么是WebAssembly
根据 Mozilla 开发者网络 (MDN) 文档中的定义,WebAssembly (WASM) 是:
一种新型代码,可以在现代 Web 浏览器中运行,并提供新功能和性能的重大提升。它不是手工编写的,而是被设计为C,C++,Rust等源语言的有效编译目标。
因此,WASM 让我们在浏览器中使用不同语言(不仅仅是 JavaScript)编写的代码,具有以下好处:
- 它快速、高效且便携。
- 它是安全的,因为代码在安全的沙盒执行环境中运行。
- 它可以在客户端运行。
因此,在上面的示例中,我们不需要担心用户在我们的服务器上运行代码,因为代码执行发生在客户端的 Web 浏览器中。
WebAssembly 并不是为了扼杀 JavaScript 而设计的。它是对 JavaScript 的补充。在 JavaScript 不合适使用场景,可以使用它。例如游戏、图像识别和图像/视频编辑等。
Pyodide
本教程使用 Pyodide 库运行 Python 代码,该代码将 CPython 解释器编译为 WebAssembly,并在浏览器的 JavaScript 环境中运行二进制文件。它带有许多预装的 Python 包。
Hello World
使用以下代码创建新的 HTML 文件:
<head>
<script src="https://cdn.jsdelivr.net/pyodide/v0.20.0/full/pyodide.js"></script>
<script>
async function main() {
let pyodide = await loadPyodide({
indexURL : "https://cdn.jsdelivr.net/pyodide/v0.20.0/full/"
});
console.log(pyodide.runPython("print('Hello, world from the browser!')"));
};
main();
</script>
</head>
在浏览器中打开文件。然后,在浏览器开发人员工具的控制台中,您应该执行如下操作:
Loading distutils
Loading distutils from https://cdn.jsdelivr.net/pyodide/v0.20.0/full/distutils.js
Loaded distutils
Python initialization complete
Hello, world from the browser!
如您所见,最后一行是在浏览器中执行 Python 代码的结果。
让我们快速看一下上面的代码:
- 首先,您可以使用 [CDN](Downloading and deploying Pyodide — Version 0.24.1) 或直接从 GitHub 版本下载并安装 [Pyodide](Downloading and deploying Pyodide — Version 0.24.1)。
- loadPyodide 加载并初始化 Pyodide wasm 模块。
- pyodide.runPython 将 Python 代码作为字符串并返回代码的结果。
Pyodide的优势
在前面的示例中,您了解了安装 Pyodide 并开始使用它是多么容易。您只需要从 CDN 导入 pyodide.js 并通过 初始化它 loadPyodide 。之后,您可以使用 pyodide.runPython("Your Python Code Here") 在浏览器中运行 Python 代码。
当您第一次下载 Pyodid 时,下载量很大,因为您正在下载完整的 CPython 解释器,但您的浏览器会缓存它,不需要再次下载。
Pyodide 限制
要第一次加载 Pyodide,需要四到五秒钟(取决于您的连接),因为您必须下载 ~10MB。此外,Pyodide 代码的运行速度比原生 Python 慢 3 到 5 倍左右。
其他选项
通常,如果要在浏览器中运行 Python,可以使用两种方法:
- 使用转译器将 Python 转换为 JavaScript。Brython、Transcrypt 和 Skulpt 都使用这种方法。
- 转换 Python 运行时以在浏览器中使用。Pyodide和PyPy.js使用这种方法。
选项 1 和 2 之间的一个主要区别是选项 1 中提到的库不支持 Python 包。也就是说,它们的下载大小比选项二中的库小得多,因此速度更快。
在本教程中,我们选择了 Pyodide,因为它具有更简单的语法并且支持 Python 包。如果您对其他选项感兴趣,请随时查看他们的文档。
Python 代码编辑器
在本节中,我们将创建一个简单的 Python 编辑器,该编辑器可以使用以下命令在浏览器中运行代码:
创建一个新项目:
$ mkdir python_editor_wasm
$ cd python_editor_wasm
创建并激活虚拟环境:
$ python3.10 -m venv env
$ source env/bin/activate
(env)$
Install Flask: 安装 Flask:
(env)$ pip install Flask
在项目的根目录中,创建一个名为 app.py 的文件,并添加以下代码:
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
def index():
return render_template('index.html')
if __name__ == '__main__':
app.run(debug=True)
在项目的根目录下创建一个templates文件夹,并在其下添加 index.html 文件。
templates/index.html:
<!doctype html>
<html class="h-full bg-slate-900">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- install tailwindcss from cdn, don't do this for production application -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- install pyodide version 0.20.0 -->
<script src="https://cdn.jsdelivr.net/pyodide/v0.20.0/full/pyodide.js"></script>
<!-- import codemirror stylings -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.48.4/codemirror.min.css" />
<!-- install codemirror.js version /5.63.3 from cdn -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.63.3/codemirror.min.js"
integrity="sha512-XMlgZzPyVXf1I/wbGnofk1Hfdx+zAWyZjh6c21yGo/k1zNC4Ve6xcQnTDTCHrjFGsOrVicJsBURLYktVEu/8vQ=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<!-- install codemirror python language support -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.63.3/mode/python/python.min.js"
integrity="sha512-/mavDpedrvPG/0Grj2Ughxte/fsm42ZmZWWpHz1jCbzd5ECv8CB7PomGtw0NAnhHmE/lkDFkRMupjoohbKNA1Q=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<!-- import codemirror dracula theme styles from cdn -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.63.3/theme/dracula.css"/>
<style>
/* set codemirror ide height to 100% of the textarea */
.CodeMirror {
height: 100%;
}
</style>
</head>
<body class="h-full overflow-hidden max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 mt-8">
<p class="text-slate-200 text-3xl my-4 font-extrabold mx-2 pt-8">Run Python in your browser</p>
<div class="h-3/4 flex flex-row">
<div class="grid w-2/3 border-dashed border-2 border-slate-500 mx-2">
<!-- our code editor, where codemirror renders it's editor -->
<textarea id="code" name="code" class="h-full"></textarea>
</div>
<div class="grid w-1/3 border-dashed border-2 border-slate-500 mx-2">
<!-- output section where we show the stdout of the python code execution -->
<textarea readonly class="p-8 text-slate-200 bg-slate-900" id="output" name="output"></textarea>
</div>
</div>
<!-- run button to pass the code to pyodide.runPython() -->
<button onclick="evaluatePython()" type="button" class="mx-2 my-4 h-12 px-6 py-3 border border-transparent text-base font-medium rounded-md shadow-sm bg-green-700 hover:bg-green-900 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-700 text-slate-300">Run</button>
<!-- clean the output section -->
<button onclick="clearHistory()" type="button" class="mx-2 my-4 h-12 px-6 py-3 border border-transparent text-base font-medium rounded-md shadow-sm bg-red-700 hover:bg-red-900 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-700 text-slate-300">Clear History</button>
<script src="/static/js/main.js"></script>
</body>
</html>
在 index.html 文件的头部,我们导入了用于样式的 Tailwind CSS、Pyodide.js 版本 0.20.0 以及 CodeMirror 及其依赖项。
UI 有三个重要组件:
- Editor:用户可以在其中编写 Python 代码。它是一个
textarea带有id的codeHTML 元素。当我们初始化时codemirror,我们让它知道我们想将这个元素用作代码编辑器。 - Output:显示代码输出的位置。它是带有
id的outputtextarea元素。当 Pyodide 执行 Python 代码时,它会将结果输出到此元素。我们还在此元素中显示了错误消息。 - Run button:当用户单击此按钮时,我们获取编辑器元素的值并将其作为字符串传递给
pyodide.runPython.当返回结果时pyodide.runPython,我们将其显示在输出元素中。
现在,在项目的根目录中,创建static/js文件夹。然后,在js文件夹下,创建一个名为 main.js 的新文件。
static/js/main.js:
// find the output element
const output = document.getElementById("output");
// initialize codemirror and pass configuration to support Python and the dracula theme
const editor = CodeMirror.fromTextArea(
document.getElementById("code"), {
mode: {
name: "python",
version: 3,
singleLineStringErrors: false,
},
theme: "dracula",
lineNumbers: true,
indentUnit: 4,
matchBrackets: true,
}
);
// set the initial value of the editor
editor.setValue("print('Hello world')");
output.value = "Initializing...\n";
// add pyodide returned value to the output
function addToOutput(stdout) {
output.value += ">>> " + "\n" + stdout + "\n";
}
// clean the output section
function clearHistory() {
output.value = "";
}
// init pyodide and show sys.version when it's loaded successfully
async function main() {
let pyodide = await loadPyodide({
indexURL: "https://cdn.jsdelivr.net/pyodide/v0.20.0/full/",
});
output.value = pyodide.runPython(`
import sys
sys.version
`);
output.value += "\n" + "Python Ready !" + "\n";
return pyodide;
}
// run the main function
let pyodideReadyPromise = main();
// pass the editor value to the pyodide.runPython function and show the result in the output section
async function evaluatePython() {
let pyodide = await pyodideReadyPromise;
try {
pyodide.runPython(`
import io
sys.stdout = io.StringIO()
`);
let result = pyodide.runPython(editor.getValue());
let stdout = pyodide.runPython("sys.stdout.getvalue()");
addToOutput(stdout);
} catch (err) {
addToOutput(err);
}
}
- 初始化了 CodeMirror,支持 Python 和 Dracula 主题。
- 初始化 Pyodide。
- 添加了一个调用
evaluatePython的函数,该函数在用户单击Run按钮时执行。它将元素的值传递给pyodide.runPythonoutput元素,并通过 在code元素中显示结果addToOutput。 - 添加了一个名为的函数
clearHistory,该函数在用户单击Clear History按钮时清除output元素。
若要在本地运行 Flask 开发服务器,请运行:
(env)$ flask run
服务现在应该在端口 5000 上运行。导航到浏览器中的 http://127.0.0.1:5000 以测试代码编辑器。
原文: Running Python in the Browser with WebAssembly | TestDriven.io