WordPress 中使用 Pyodide
之前的系列文章中曾经介绍过如何在后端使用 Python 程序,那么是否也可以在前端运行 Python 程序呢,答案就是 Pyodide。Pyodide 实现了使用 Emscripten 把 CPython 编译到 WebAssembly 虚拟机上,而目前主流的浏览器都集成了 WebAssembly。
本篇文章介绍如何在 JavaScript 程序中访问 Python 程序中的变量、运行 Python 程序中的函数,以及在 Python 程序中访问 JavaScript 程序中的变量、调用函数。另外还可以用 Python 程序画图,获取 WordPress 文章等。这些功能将会放到 WordPress 的文章中来体现。
一、安装 Pyodide
如果不安装到本地,使用 pyodide 比较简单,直接在 Script 标签中引用 pyodide.js 即可,后续使用的代码会从 CDN 中下载。
<script type="text/javascript" scr="https://cdn.jsdelivr.net/pyodide/v0.23.1/full/pyodide.js"></script>
安装到本地需要从 Pyodide 官网下载程序代码,包括完整包和核心包两种,核心包一般在 node 环境中使用,相关组件会从 CDN 中自动下载。本例介绍完整包的安装。
下载后的代码解压到 WordPress 自己插件的主目录下,比如:
/wp-content/plugins/my-plugin/pyodide
二、Web 服务器配置文件中添加应用类型 wasm
比如 Nginx,在配置文件 mime.types 中增加下面一行:
application/wasm wasm;
三、引用 pyodide.js
在插件的主程序中添加下面的代码:
add_action( 'wp_enqueue_scripts', "pyodide_enqueue");
function pyodide_enqueue() {
wp_enqueue_script( 'pyodide', plugins_url( '/pyodide/pyodide.js', __FILE__ ), array('jquery'));
wp_enqueue_script( 'components', plugins_url( '/js/components.js', __FILE__ ), array('pyodide'));
}
四、新建 WordPress 文章
在文章中插入 “自定义HTML” 区块,输入下面的内容并保存:
<div id="pct">
<textarea id="poutput" style="width: 100%;" rows="10" disabled></textarea>
</div>
<div id="fetch_output">
<h5>This is output of fetching post with python</h5>
</div>
五、功能代码
在插件主目录创建子目录 js,然后创建 js/components.js 文件,下面的功能代码放在该文件中,这些代码的运行结果会显示在上面创建的文章里。
1、显示 Python 版本号
(function ($) {
document.getElementById('pct') && $(document).ready( function() {
const output = $("#poutput");
function DisplayResults(p, s) {
output[0].value += ">>>" + p + "\n" + s + "\n";
}
output[0].value = "Start running ...\n";
async function run() {
//异步装载pyodide并实例化对象
const pyodide = await loadPyodide();
//运行pythond代码
DisplayResults("Python Version", pyodide.runPython(`
import sys
sys.version
`));
......
}
run();
});
})(jQuery);
2、运行 Python 表达式
let output = pyodide.runPython("1+2");
DisplayResults("Python run '1+2'", output);
3、加载 Python 包
//javascript程序中加载Python包
await pyodide.loadPackage('numpy');
pyodide.runPython('import numpy as np');
let np_array = pyodide.runPython('np.empty([3,2], dtype = float)');
np_array = np_array.toJs();
DisplayResults("Numpy array to Javascript array", np_array);
//或者使用micropip在Python程序中加载
await pyodide.loadPackage("micropip");
pyodide.runPythonAsync(`
import micropip
await micropip.install("numpy")
import numpy as np
`);
4、Python 程序中创建 DOM 元素
pyodide.runPython(`
from js import document
from js import window
//调用javascript对象document
div = document.createElement("div")
div.innerHTML = "<h1>This h1 title was created from Python</h1>"
//访问javascript对象window,调用jquery
window.jQuery("#pct").append(div)
`);
5、Python 程序中创建 DOM 事件响应
pyodide.runPython(`
from js import document
//python事件响应程序
def handle_clear_output(event):
//访问javascript对象document
output_area = document.getElementById("poutput")
output_area.value = ""
//创建按钮DOM元素
clear_button = document.createElement("button")
clear_button.innerHTML = "Clear"
clear_button.onclick = handle_clear_output
pct = document.getElementById("pct")
pct.insertBefore(clear_button, pct.childNodes[0])
`);
6、JavaScript 程序中访问 Python 变量
pyodide.runPython(`
import numpy as np
v = np.arange(1, 10, 2)
`);
//调用globals获取python变量值
DisplayResults("This is from python variable", pyodide.globals.get('v').toJs());
7、JavaScript 程序中调用 Python 函数
//调用globals访问python函数
let v = pyodide.globals.get('np').ones(pyodide.toPy([2,3])).toJs();
DisplayResults("Call python function from js", v);
8、Python 程序中调用 JavaScript 函数
//调用globals设置python程序中使用的javascript函数
pyodide.globals.set('jlog', msg => msg && DisplayResults("Call js function from python", msg));
pyodide.globals.set('js_fun', function() {
return 1 * 2
});
pyodide.runPython(`
jlog(f'1x2 = : {js_fun()}')
`);
9、调用 Python 程序画图
await pyodide.loadPackage(['scipy', "matplotlib"]);
pyodide.runPython(`
from js import document
import numpy as np
import scipy.stats as stats
import matplotlib.pyplot as plt
import io, base64
div_container = document.createElement('div')
div_container.innerHTML = """
<h5>Use matplotlib to draw picture, click Plot button</h5>
<button onclick="pyodide.globals.get('plot_image')()">Plot</button>
<br><br>
<img id="fig" />
"""
document.getElementById("pct").appendChild(div_container)
//matplotlib画图并生成图片
def plot_image():
t = np.arange(0, 3.1, 0.1)
s = 1 + np.sin(2 * np.pi * t)
fig, ax = plt.subplots()
ax.plot(t, s)
ax.set(xlabel='time (s)', ylabel='voltage (mV)',
title='Draw sin curve')
ax.grid()
//画的图保存到字节缓冲里
buf = io.BytesIO()
fig.savefig(buf, format='png')
buf.seek(0)
//图片数据编码成DOM元素img所使用的数据
img = 'data:image/png;base64,' + base64.b64encode(buf.read()).decode('UTF-8')
img_tag = document.getElementById('fig')
img_tag.src = img
buf.close()
`);
10、Python 程序中获取 WordPress 文章
pyodide.runPythonAsync(`
import json
from js import document
//pyfetch实际调用javascript的fetch函数
from pyodide.http import pyfetch
resp = await pyfetch('/wp-json/wp/v2/posts/1')
data = await resp.json()
div_f = document.createElement('p')
div_f.innerHTML = data["title"]["rendered"]
document.getElementById("fetch_output").appendChild(div_f)
`);
11、打开浏览器标签
pyodide.runPythonAsync(`
import webbrowser
webbrowser.open_new_tab("https://kflyo.com")
`);
最后打开 WordPress 中新建的文章,就可以看到运行结果了。
通过以上代码可以看到使用 Pyodide 在前端运行 Python 程序代码还是很方便和强大的,上面的 Python 程序运行在浏览器 JavaScript 主线程里,如果程序比较耗时会阻塞主线程,界面会停止响应,这种情况下可以把程序放到 Web Worker 里去执行。
另外基于 Pyodide 的 PyScript(Anaconda 开发) 可以把 Python 程序直接放到 Html 代码中。