Pyodideを使ってみる その1(ブラウザでのPythonコードの実行)
はじめに
今回はWebブラウザでPythonを実行できる、Pyodideを試してみました。
Pyodideは去年のNode-RED Conの際に、知り合いの方から教えてもらいました。私がNode-RED用に開発したpython-venvノードはシステムのPythonを利用するもので、事前にPythonをインストールしておく必要がありました。しかし、PyodideだとPythonがインストールされていなくても実行できるようです。
▼Pyodide は CPython を WebAssembly/ Emscriptenに移植したものと説明されています。
制約はありそうですが、便利に使えそうならノード化したいなと思っています。
▼以前の記事はこちら
関連情報
▼Pyodideのページはこちら。
▼PyodideのGitHubのページはこちら。リポジトリがさらに分かれています。
▼Pyodideの概要やできることについては、以下の記事が分かりやすかったです。
https://qiita.com/suzuki_sh/items/aa06142f2445b05b6331
▼コンソールのように実行できるデモがあります。ブラウザで動くのは不思議な感じですね。
https://pyodide.org/en/stable/console.html
サンプルを実行してみる
▼以下のQuickstartを参考に進めます。
https://pyodide.org/en/stable/usage/quickstart.html
まずは以下のサンプルを試してみました。
<!doctype html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/pyodide/v0.27.6/full/pyodide.js"></script>
</head>
<body>
Pyodide test page <br>
Open your browser console to see Pyodide output
<script type="text/javascript">
async function main(){
let pyodide = await loadPyodide();
console.log(pyodide.runPython(`
import sys
sys.version
`));
pyodide.runPython("print(1 + 2)");
}
main();
</script>
</body>
</html>
よく考えたらこのホームページ自体がWordpressで作成しているもので、HTMLを埋め込むことができます。
▼以下にサンプルのHTMLを埋め込んでいます。
Pyodide test pageOpen your browser console to see Pyodide output
▼ちゃんと実行されていれば、以下のようにブラウザのコンソールでPyodideの出力を確認するように表示されているかと思います。

▼Googleブラウザの開発者ツールのコンソールにも出力されていました。バージョンや1+2の結果が出力されています。

他のサンプルも埋め込んでみたのですが、うまく表示されないことがあったので埋め込んではいません。ファイルとして保存し、ブラウザで開くと実行されていました。
JavaScript側とPyodide側でデータのやりとりもできるようです。サンプルをもとに、以下のようにChatGPTに書いてもらいました。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Pyodide Data Sharing Example</title>
<script src="https://cdn.jsdelivr.net/pyodide/v0.27.6/full/pyodide.js"></script>
</head>
<body>
<h2>Pyodide Data Sharing Example</h2>
<button onclick="runExample()">Run Example</button>
<pre id="output"></pre>
<script>
async function runExample() {
// Pyodideの読み込み
const pyodide = await loadPyodide();
// JavaScriptのデータ
const jsData = [1, 2, 3, 4, 5];
// JavaScriptのデータをPythonに渡す
pyodide.globals.set("js_data", jsData);
// Pythonコードの実行
const pythonCode = `
total = sum(js_data)
message = f"Python received: {js_data}\\nSum: {total}"
`;
await pyodide.runPythonAsync(pythonCode);
// Pythonから結果を取得
const result = pyodide.globals.get("message");
// 結果を表示
document.getElementById("output").textContent = result;
// 使用後は変数を削除してメモリを解放
pyodide.globals.delete("js_data");
pyodide.globals.delete("message");
}
</script>
</body>
</html>
▼JavaScriptのデータを受け取ってPythonで処理し、JavaScriptに戻すことができました。

パッケージを利用した処理
PyodideはPythonの標準ライブラリはもちろん、すでに組み込まれているパッケージも利用できます。
▼パッケージの一覧はこちら。numpyやopencv-pythonもあります。
https://pyodide.org/en/stable/usage/packages-in-pyodide.html
この一覧にあるものを利用するのが使いやすそうです。
Matplotlibを利用したサンプルを、ChatGPTに書いてもらって試してみました。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Pyodide + Matplotlib Graph</title>
<script src="https://cdn.jsdelivr.net/pyodide/v0.23.4/full/pyodide.js"></script>
</head>
<body>
<button id="plotBtn">Draw Graph</button>
<div id="output">Click the button to draw the graph.</div>
<canvas id="canvas" style="border:1px solid black;"></canvas>
<script>
let pyodide = null;
let isInitialized = false;
async function initializePyodide() {
document.getElementById("output").textContent = "Initializing Pyodide...";
pyodide = await loadPyodide();
await pyodide.loadPackage("matplotlib");
await pyodide.runPythonAsync(`
import matplotlib
matplotlib.use('AGG')
import matplotlib.pyplot as plt
import io
import base64
`);
isInitialized = true;
document.getElementById("output").textContent = "Initialization complete. Drawing...";
}
document.getElementById("plotBtn").onclick = async () => {
if (!isInitialized) {
await initializePyodide();
} else {
document.getElementById("output").textContent = "Drawing...";
}
const pyCode = `
fig, ax = plt.subplots(figsize=(4, 3))
ax.plot([1, 2, 3, 4], [10, 20, 15, 25], marker='o')
ax.set_title("Simple Line Graph")
buf = io.BytesIO()
fig.savefig(buf, format='png')
buf.seek(0)
img_base64 = base64.b64encode(buf.read()).decode('ascii')
plt.close(fig)
img_base64
`;
try {
const imgBase64 = await pyodide.runPythonAsync(pyCode);
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
const img = new Image();
img.onload = () => {
canvas.width = img.width;
canvas.height = img.height;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0);
document.getElementById("output").textContent = "Graph drawn successfully.";
};
img.src = "data:image/png;base64," + imgBase64;
} catch (err) {
document.getElementById("output").textContent = "Error: " + err.message;
console.error(err);
}
};
</script>
</body>
</html>
▼以下のようにグラフが表示されました。

ファイルの読み書きは?
PyodideのOSの機能へのアクセスについて気になったので、ファイルシステムはどうなっているのか調べてみました。
▼Pyodideのファイルシステムに関するページはこちら
https://pyodide.org/en/stable/usage/file-system.html
ブラウザで実行する場合、セキュリティ上の問題でOSの機能にアクセスすることはできません。JavaScriptの場合、ブラウザ上のV8エンジンはOSの機能にアクセスするための仕組みが用意されておらず、Node.jsのようにOS上で動作するランタイムはアクセスできます。
Pyodideも同じように、OSの機能にはアクセスできません。その代わり、仮想的なファイルシステムを利用することができるようです。
ChatGPTに以下の簡単なサンプルコードを書いてもらいました。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<title>Pyodide ファイル読み書き</title>
<script src="https://cdn.jsdelivr.net/pyodide/v0.23.4/full/pyodide.js"></script>
</head>
<body>
<button id="runBtn">ファイル読み書き</button>
<div id="output" style="white-space: pre-wrap; margin-top: 10px; border: 1px solid #ccc; padding: 1em;">出力結果がここに表示されます。</div>
<script>
let pyodideReady = false;
let pyodide = null;
async function initPyodide() {
if (!pyodideReady) {
document.getElementById("output").textContent = "Pyodideを初期化中...";
pyodide = await loadPyodide();
pyodideReady = true;
document.getElementById("output").textContent = "Pyodideの初期化が完了しました。ボタンを押して実行してください。";
}
}
document.addEventListener("DOMContentLoaded", async () => {
await initPyodide(); // ページ読み込み時に初期化
});
document.getElementById("runBtn").addEventListener("click", async () => {
if (!pyodideReady) {
await initPyodide();
}
document.getElementById("output").textContent = "Pythonコードを実行中...";
const code = `
# 書き込み
with open("example.txt", "w", encoding="utf-8") as f:
f.write("こんにちは Pyodide の世界!\\nこれはテストファイルです。")
# 読み込み
with open("example.txt", "r", encoding="utf-8") as f:
content = f.read()
content
`;
try {
const result = await pyodide.runPythonAsync(code);
document.getElementById("output").textContent = "ファイルの内容:\n" + result;
} catch (err) {
document.getElementById("output").textContent = "エラー: " + err.message;
console.error(err);
}
});
</script>
</body>
</html>
▼以下のように表示されました。

Pyodideで仮想的なファイルの読み書きができました。
最後に
システムで実行するPythonに比べると制約を感じますが、インストール不要で実行できるのは面白い仕組みです。
Node-REDでも簡単に利用できないか試したいなと思っています。
▼Node.js用のパッケージがありました。
https://www.npmjs.com/package/pyodide