0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

marimoでPython関数をPyScriptとして実行する「marimo-pys」を作った

0
Posted at

概要

marimoからPyScriptを使って、ブラウザ上でPythonのコードを実行したい。

そんな用途のために、Pythonの非同期関数を渡すだけでPyScriptを実行し、結果をHTML要素として表示できるパッケージ「marimo-pys」を作りました。

marimo-pysとは

marimo-pysは、marimoのセルからPyScriptを実行するための小さなパッケージです。

run_pyscript()async def で定義したPython関数を渡すと、関数のソースコードを取り出し、PyScriptで実行するHTMLを生成します。生成したHTMLはiframeに格納され、marimo.Html として返されます。

つまり、marimo側で定義した関数を、ブラウザ側の独立したPython実行環境で実行する仕組みです。

インストール

pipの場合:

pip install marimo marimo-pys

uvでプロジェクトに追加する場合:

uv add marimo marimo-pys

すでにmarimoを導入していれば、marimo-pys だけ追加すればOKです。

また、パッケージをインストールせず、GitHubの marimo_pys/__init__.py のコードをmarimoのセルにコピーして使うこともできます。

基本的な使い方

次のコードをmarimoのセルに記述します。

from marimo_pys import run_pyscript

async def hello(js, data):
    element = js.document.createElement("p")
    element.textContent = f"こんにちは、{data['name']}"
    js.document.body.appendChild(element)

run_pyscript(hello, data={"name": "PyScript"}, height="120px")

ブラウザ側で hello() が実行され、iframe内に「こんにちは、PyScript!」と表示されます。

ポイントは、通常のPython関数ではなく async def で定義することです。run_pyscript() は関数を次のような形で呼び出します。

await hello(js, data)

js はPyScript側のJavaScript連携用モジュール、datarun_pyscript() に指定したデータです。JavaScriptのDOM APIを利用して、HTML要素を生成・変更できます。

関数の戻り値を自動表示する仕組みではないため、画面への出力は上記のようにDOMを操作するなどして行います。

なぜasync関数を渡すだけで動くのか

内部では、おおむね次の手順を踏んでいます。

  1. inspect.getsource() でPython関数のソースコードを取得する
  2. ソースコードに、関数を呼び出すための処理を追加する
  3. コードをBase64にエンコードし、PyScript用のHTMLに埋め込む
  4. HTMLを iframe srcdoc に格納し、marimoのHTMLオブジェクトとして返す

実行するのは、marimoのPython環境ではなくiframe内のPyScriptです。

また、data に指定した辞書はJSONを経由して受け渡しています。

実装で苦労したところ:PyScriptのURL解決

当初は、PyScriptを読み込むHTMLを生成して、そのままiframeに渡せば動くと考えていました。

ところが、iframe srcdoc で生成したHTMLでは、PyScriptの内部処理がURLを解決できず、正常に起動しない問題がありました。

原因は、PyScriptが内部で利用するpolyscriptの相対URL解決処理です。

通常のWebページであれば location.href を基準に相対URLを解決できます。しかし、srcdoc 内では location.hrefabout:srcdoc になるため、その値を基準URLにするとURLの生成に失敗します。

そこで、JavaScriptの URL クラスにモンキーパッチを当てることにしました。

実装の要点を抜粋すると、次のようになります。

const NativeURL = window.URL;

class PatchedURL extends NativeURL {
  constructor(url, base) {
    const b = base == null ? '' : String(base);

    if (b === '' || b.startsWith('about:')) {
      base = 'https://pyscript.net/releases/2026.7.3/';
    }

    super(url, base);
  }
}

window.URL = PatchedURL;

基準URLが空、または about: で始まる場合に、PyScriptのリリースURLへ置き換えています。

これをPyScriptの core.js を読み込む前に適用することで、問題となっていた相対URLの解決を回避しています。

なお、これはPyScript 2026.7.3で確認した回避策です。ブラウザ標準のURLクラスの挙動をiframe内で変更しているため、PyScriptの更新時には動作確認が必要です。

その他の機能

run_pyscript() には、表示や実行環境を調整するためのオプションも用意しています。

引数 用途
width, height iframeの表示サイズ
data Python関数に渡すデータ
config PyScriptの設定
pys_type mpypypy-game の選択
pys_version 使用するPyScriptのバージョン
terminal ターミナル表示の有効化
add_script, add_module, add_css JavaScriptやCSSの追加
body_style, iframe_style CSSスタイルの指定
iframe_sandbox iframeのsandbox属性の指定
add_dangerous_html HTMLを直接追加

標準の pys_typempy(MicroPython)で、py を指定するとPyodideを使用できます。

例えば、Pyodideを使いたい場合は次のように指定します。

run_pyscript(hello, pys_type="py", data={"name": "Pyodide"})

使用上の注意点

関数のソースコードを取り出して、ブラウザ側の独立したPython環境で実行するため、marimo側の変数やインポート済みモジュールをそのまま参照することはできません。

必要な値は data で渡し、必要なPythonモジュールは関数の内部でインポートしてください。data に渡せるのはJSONに変換できる値です。

また、関数に付けたデコレータには対応していません。HTMLを直接追加する add_dangerous_html には、信頼できない内容を渡さないでください。

おわりに

marimo上でPythonの処理を書きつつ、その一部をPyScriptによってブラウザ側で実行できるようにしてみました。

run_pyscript() にasync関数を渡すだけなので、PyScript用のHTMLを毎回手書きする必要はありません。

実装ではpolyscriptのURL解決に少し苦労しましたが、JavaScriptのURLクラスをモンキーパッチすることで回避しています。

ソースコードはGitHubで公開しています。興味があれば使ってみてください。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?