Pyodideを使ってみる その1(ブラウザでのPythonコードの実行)

はじめに

 今回はWebブラウザでPythonを実行できる、Pyodideを試してみました。

 Pyodideは去年のNode-RED Conの際に、知り合いの方から教えてもらいました。私がNode-RED用に開発したpython-venvノードはシステムのPythonを利用するもので、事前にPythonをインストールしておく必要がありました。しかし、PyodideだとPythonがインストールされていなくても実行できるようです。

▼Pyodide は CPython を WebAssembly/ Emscriptenに移植したものと説明されています。

https://pyodide.org/en/stable

 制約はありそうですが、便利に使えそうならノード化したいなと思っています。

▼以前の記事はこちら

Node-REDのノードを作成してみる その1(python-venv)

はじめに  今回はNode-REDでPythonの仮想環境を利用できるノードを作成してみました。  これまでNode-RED MCU用のノードを作成したことはありますが、Node-RED用は2つ目…

Pythonの仮想環境を作成する(venv、Windows)

はじめに  今回はPythonの仮想環境の作成についてまとめてみました。  Pythonを利用したNode-REDのノードを開発するときに仮想環境を詳しく調べていました。作成した仮…

関連情報

▼Pyodideのページはこちら。

https://pyodide.org/en/stable

▼PyodideのGitHubのページはこちら。リポジトリがさらに分かれています。

https://github.com/pyodide

▼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 page
Open 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

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です