Python Monthly Topics

WebブラウザでPythonが動作する!PyScriptの詳解

鈴木たかのり@takanoryです。今月の「Python Monthly Topics」では、Webブラウザ上でPythonが動作するPyScriptについて、内部構造なども含めて詳しく解説したいと思います。

PyScript公式サイト(https://pyscript.net/)
PyScript公式サイト

WarningPyScriptは現在非常に活発に開発が進んでいるプロダクトのため、将来的にこの記事のサンプルコードが動かなくなる可能性があります。記事執筆時点では最新バージョンであるPyScript 2023.03.1で動作確認しています。うまく動かない場合はPyScriptの公式ドキュメントなどを参照してみてください。

PyScriptとは?

PyScriptは公式サイトに「Run Python in Your HTML」と書いてあるとおり、HTMLの中にPythonのコードを書くと、それがWebブラウザ上で実行されるというものです。

まずは説明の前に、実際に動いているところを見てもらった方が早いと思います。以下はシンプルなPyScriptを含んだHTMLファイルです。

demo.html - 現在時刻を出力する
<html lang="ja">
  <head>
    <meta charset="utf-8"/>
    <link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
    <script defer src="https://pyscript.net/latest/pyscript.js"></script>
  </head>
  <body>
    <py-script>
      from datetime import datetime
      now = datetime.now()
      print(f"今は{now:%Y年%m月%d日 %H:%M:%S}です。")
    </py-script>
  </body>
</html>

このソースコードをdemo.htmlなどの適当なファイル名で保存してWebブラウザで開きます。最初にロードなどが実行され少し時間がかかりますが、HTMLの中でPythonが実行されて現在日時が表示されます。

demo.htmlを実行
demo.htmlを実行

PyScriptのコードの基本

ここでは簡単にPyScriptのコードの書き方について説明します。とはいえ、ほとんど見たままです。

まず、<head>タグの中でpyscript.csspyscript.jsを読み込みます。このpyscript.jsがPyScriptの本体とも言えるもので、このJavaScriptファイルを読み込むことにより、HTMLの中でPythonのコードが実行されるようになります。

headタグの中でCSSとJavaScriptを読み込む
  <head>
    <meta charset="utf-8"/>
    <link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
    <script defer src="https://pyscript.net/latest/pyscript.js"></script>
  </head>

そして<body>タグの中の<py-script>の中にPythonのコードを書きます。この<py-script>などのタグがpyscript.jsによってHTMLの中で使えるようになっているのが、PyScriptの基本的な機能となっています。

最後のprint()関数の出力した文字列が画面上に表示されます。

py-scriptタグの中にPythonのコードを書く
  <body>
    <py-script>
      from datetime import datetime
      now = datetime.now()
      print(f"今は{now:%Y年%m月%d日 %H:%M:%S}です。")
    </py-script>
  </body>

他にもいろいろとPyScriptでできることはありますので、詳細については後述します。

PyScriptの開発元

PyScriptの開発はAnaconda社が中心となって行っています。Anaconda社はデータサイエンス用のPythonディストリビューションのAnacondaを作成、配布している会社です。Anaconda社が開発の中心であるため、PyScriptもデータサイエンス関係のライブラリが豊富です。具体的な利用方法については後述します。

PyScriptは2022年4月にアメリカのソルトレイクシティで開催されたPyCon US 2022キーノートで、CEOのPeter Wang氏によって発表されました。発表の時は会場でも「これはなんだ?どうなっているんだ?」という感じで、とてもざわついていたそうです。発表の動画はYouTubeで公開されています。さまざまなデモを交えて紹介されており、PyScriptの可能性が感じられる発表となっています。

PyScriptはGitHub上でオープンに開発が進められています。メンテナーとしても現在はAnaconda社以外の方も参加しているようです。ライセンスもApache License 2.0となっています。PyScriptのソースコード等は以下のリポジトリで確認できます。

PyScriptのGitHubリポジトリ
URL:https://github.com/pyscript/pyscript

PyScriptでできること

最初に示したシンプルなコードだけでなく、PyScriptではさまざまなことができます。ここでは、サンプルコードを交えてPyScriptでどういうコードが記述でき、どのように動作するかについて説明します。

display()関数での出力

先ほどprint()関数で結果を出力した場合は、ターミナルのような表示になっていましたが、display()関数を使用するとHTMLの中にそのまま埋め込まれます。なお、以降のコードでは<head>部分などは共通なので省略しています。

display1.html - display()関数で結果を出力する
  <body>
    <py-script>
      from datetime import datetime
      now = datetime.now()
      display(f"今は{now:%Y年%m月%d日 %H:%M:%S}です。")
    </py-script>
  </body>

このHTMLをWebブラウザで表示すると以下のようになります。

display1.htmlの実行結果
display1.htmlの実行結果

また、display()関数にはtarget引数を指定できます。target引数を指定すると、指定されたidを持つ要素に結果が出力されます。以下のコード例では"now"というid<span>要素に現在時刻が出力されています。

display2.html - target引数を指定する
  <body>
    <py-script>
      from datetime import datetime
      now = datetime.now()
      display(f"{now:%Y年%m月%d日 %H:%M:%S}", target="now")
    </py-script>
    <ul>
      <li>今は<b><span id="now">現在日時</span></b>です。</li>
    </ul>
  </body>
display2.htmlの実行結果
display2.htmlの実行結果

display()関数ではデフォルトで<div>タグが挿入されるため、出力が改行されていていまいちです。display()関数のappend引数にFalseを指定すると、<div>タグが追加されずにそのまま文字列が出力されます(デフォルトはTrue⁠。以下の例では<span>タグの中に出力しているため、改行されずに出力されます。また、もともとあった現在日時という文字列が置換されるため消えています。

display3.html - append=Falseを指定する
  <body>
    <py-script>
      from datetime import datetime
      now = datetime.now()
      display(f"{now:%Y年%m月%d日 %H:%M:%S}", target="now", append=False)
    </py-script>
    <ul>
      <li>今は<b><span id="now">現在日時</span></b>です。</li>
    </ul>
  </body>
display3.htmlの実行結果
display3.htmlの実行結果

display()関数のリファレンスは以下から参照できます。

Element()を使用したHTMLの書き換え

PyScriptではHTML中の要素を取得して、書き換えることも可能です。この機能を使うことでより柔軟にHTMLを書き換えられます。Element()で指定したHTML要素を取得し、innerHTMLにHTML文字列を代入することで書き換えが可能です。innerTextに代入すると文字列として書き換えられます<>はエスケープされます⁠⁠。ここでは文字列として<b>タグを指定することで、太字になっていることが確認できます。

element.html - HTMLの要素を書き換える
  <body>
    <div id="now"></div>
    <py-script>
      from datetime import datetime
      now = datetime.now()

      now_div = Element("now")
      now_div.element.innerHTML = f"今は<b>{now:%Y年%m月%d日 %H:%M:%S}</b>です。"
    </py-script>
  </body>
element.htmlの実行結果
element.htmlの実行結果

Element APIのリファレンスは以下から参照できます。

<py-repl>タグによる対話モードの実行

PyScriptには<py-repl>というタグが用意されており、このタグを使用するとPythonの対話モードがWebブラウザ上で動作します。

repl1.html - PyScript上で対話モードを実行
  <body>
    <py-repl></py-repl>
  </body>

このコードをWebブラウザで表示すると以下のような表示になります。Jupyter NotebookやGoogle Colaboratoryを使ったことがある方は、似たような表示だと感じると思います。ただJupyter Notebookなどと異なるのは、Webブラウザの中だけで動作しているということです。PythonがインストールされていないPC、スマートフォン上のWebブラウザでも動作します。

対話モードを表示
対話モードを表示

入力フィールドには複数行のPythonコードが書けます。Pythonコードを実行するにはShiftEnterを入力します。print()関数などを指定しなくても、最後の行の内容が出力されます。このあたりの動作もJupyter Notebookと似ています。

コードを実行
コードを実行

auto-generate="true"引数を指定すると、実行するごとに新しい対話モードの入力領域が表示されます。ますますJupyter Notebookっぽいです。

repl2.html - auto-generate="true"を指定
  <body>
    <py-repl auto-generate="true"></py-repl>
  </body>

以下はPyScriptの対話モードでいくつかのコードを実行した例です。2番目の実行結果より、now変数が保持されていることがわかります。また、sys.version_infoによりPython 3.10.2が動作していることがわかります。つまり、PyScriptでは構造的パターンマッチが使用できます。

対話モードで複数のコードを実行
対話モードで複数のコードを実行

<py-repl> タグのリファレンスは以下から参照できます。

イベントの取得

ここまではHTMLを表示したときにPyScriptが実行され、その結果が画面に表示されるという例だけを扱っていました。PyScriptでは各種イベントをトリガーにして関数を実行することもできます。

以下のコードではbuttonタグにpy-clickという属性を指定してあります。py-click属性には要素をクリックしたときのイベントを指定できます。この場合はget_now()関数が実行されます。get_now()関数の中では現在時刻を取得して、その結果をid="now"の要素に表示します。

click.html - ボタンクリックで関数を実行
  <body>
    <button py-click="get_now()" class="py-button">現在時刻を取得</button>
    <div id="now"></div>
    <py-script>
      from datetime import datetime

      def get_now():
          now = datetime.now()
          display(f"{now:%Y年%m月%d日 %H:%M:%S}", target="now")
    </py-script>
  </body>

このコードを実行すると以下のように、ボタンをクリックするごとに現在時刻が表示されます。なお、このサンプルコードでは日時を表示するたびに行が増えてきますが、display()関数にappend=False引数を指定すると日時の表示が1つだけになります。

ボタンをクリックして現在時刻を取得
ボタンをクリックして現在時刻を取得

複数のボタンを配置して、それぞれがクリックされたときに異なる処理を実行することも可能です。以下のサンプルコードでは3つのボタンにそれぞれ「グー」⁠チョキ」⁠パー」を割り当てて、簡単なジャンケンゲームを実現しています。

janken.html - 3つのボタンでジャンケン
  <body>
    <button py-click="janken(hand=0)" class="py-button">グー</button>
    <button py-click="janken(hand=1)" class="py-button">チョキ</button>
    <button py-click="janken(hand=2)" class="py-button">パー</button>
    <div id="result"></div>
    <py-script>
      import random

      HANDS = ("グー", "チョキ", "パー")
      def janken(hand):
          my_hand = random.randrange(3)
          result = ("あいこ", "わたしの勝ち", "あなたの勝ち")[(hand - my_hand) % 3]
          display(f"あなた:{HANDS[hand]}、わたし:{HANDS[my_hand]}、{result}", target="result")
    </py-script>
  </body>

PyScriptではクリック以外にも、さまざまなイベントに対応しています。以下のページにJavaScriptのイベントとPyScriptでのイベント名の対応表があります。参考にしてみてください。

モジュール分割

多くのテキストエディターでは<py-script>の中のPythonのコードをPythonとは認識していません。そのため、シンタックスハイライトや自動インデントがされず、プログラムを書くのが面倒です。Pythonのコード部分を*.pyファイルに保存し、PyScriptの中からモジュールとして読み込むことで解決できます。

PyScriptではモジュールに分割した形式のファイルを読み込んで使用できます。

ここでは、先ほどのジャンケンの例janken.htmlをHTMLとPythonのモジュールに分割します。Pythonのモジュールは以下のような内容です。<py-script>タグの中にあったPythonコードをモジュールとして切り出しました。

janken.py - ジャンケンのPythonコード
import random

HANDS = ("グー", "チョキ", "パー")


def janken(hand):
    my_hand = random.randrange(3)
    result = ("あいこ", "わたしの勝ち", "あなたの勝ち")[(hand - my_hand) % 3]
    display(f"あなた:{HANDS[hand]}、わたし:{HANDS[my_hand]}{result}", target="result")

HTMLは以下のようになります。<py-config>タグの中のfetchセクションに、モジュールとして読み込むファイルjanken.pyを指定します。そして、そのモジュールを<py-script>タグのsrc属性に指定します。これで、同じように動作します。

janken2.html - 3つのボタンでジャンケン
  <body>
    <button py-click="janken(hand=0)" class="py-button">グー</button>
    <button py-click="janken(hand=1)" class="py-button">チョキ</button>
    <button py-click="janken(hand=2)" class="py-button">パー</button>
    <div id="result"></div>
    <py-config>
      [[fetch]]
      files = ["./janken.py"]
    </py-config>
    <py-script src="./janken.py">
    </py-script>
  </body>

ただし、ローカルのHTMLファイルをWebブラウザで直接開いて実行する場合は、モジュールなどPyScriptから別のファイルを読み込むことができません。以下のようなエラーが表示されます。エラーメッセージには「Access to local files is not available when directly opening a HTML file」日本語に訳すと「HTMLファイルを直接開いた場合は、ローカルファイルにはアクセスできません」と書いてあります。

ファイルを直接開くとエラー
ファイルを直接開くとエラー

この場合は、janken2.htmljanken.pyが存在するディレクトリで、簡易的なHTTPサーバーを起動すると解決します。コマンドラインで以下のようにpython3 -m http.serverを実行してHTTPサーバーを起動します。それからhttp://localhost:8000/janken2.htmlにアクセスすると、モジュールが読み込まれて正しく動作します。

コマンドラインでHTTPサーバーを起動
$ python3 -m http.server
Serving HTTP on :: port 8000 (http://[::]:8000/) ...
::1 - - [30/Mar/2023 23:34:17] "GET /janken2.html HTTP/1.1" 304 -
::1 - - [30/Mar/2023 23:34:18] "GET /janken.py HTTP/1.1" 304 -
ジャンケンプログラムが実行できた
ジャンケンプログラムが実行できた

モジュールは別のURLからダウンロードも可能です。その場合はfetchセクションにfromでアクセス先のURLを指定します。詳細は以下のドキュメントを参照してください。

ファイルを読み込み

<py-config>タグのfetchセクションを使用して、ファイルなどリソースの読み込みもできます。ここでは別のURLから取得する例を示すために、PyCon JP 2022のWebページ用のリポジトリから、タイムテーブル情報のCSVtimetable.csvを読み込みます。CSVの中の言語の列を調べて、言語ごとの発表の数を数えます。

talks.html - timetable.csvを読み込んで言語ごとの発表数を数える
  <body>
    <py-config>
      [[fetch]]
      from = "https://raw.githubusercontent.com/pyconjp/pycon.jp.2022/develop/content/"
      files = ["timetable.csv"]
    </py-config>
    <div id="result"></div>
    <py-script>
      import csv

      with open("timetable.csv") as f:
          languages = [row["language"] for row in csv.DictReader(f)]
      for lang in set(languages):
          display(f"{lang}: {languages.count(lang)}", target="result")
    </py-script>
  </body>

実行すると、結果は以下のようにCSVを読み込んで正しく発表の数を数えています。

日本語と英語の発表の数
日本語と英語の発表の数

サードパーティ製のライブラリを使用する

PyScriptではあらかじめ用意されたサードパーティ製のライブラリを使用できます。機械学習、データ分析系のライブラリや有名なライブラリが提供されています。

サードパーティ製のライブラリを使用する場合は<py-config>タグの中に使用するライブラリを指定します。次のコードでは<py-config>の中でpandasnumpyをパッケージとして指定しています。そして<py-script>の中でこの2つのライブラリを使用してPandasのDataFrameを生成しています。

pyconfig.html - pandasを使用
  <body>
    <py-config>
      packages = ["pandas", "numpy"]
    </py-config>
    <p>Pandasのデータフレームを表示</p>
    <py-script>
      import pandas as pd
      import numpy as np

      df = pd.DataFrame(np.random.randn(6, 4), columns=list("ABCD"))
      display(df)
    </py-script>
  </body>

display()関数にDataFrameのオブジェクトを渡すと、HTMLの表として描画されます。これはdisplay()関数が、HTMLで表現できるオブジェクトはHTMLとしてそのまま出力するためです。

DatFrameが表形式で表示される
DatFrameが表形式で表示される

Matplotlibでグラフ描画

他にもグラフ描画で使われるMatplotlibにも対応しています。以下のコードのように、通常のMatplotlibと同様にグラフを作成します。

matplotlib.html - Matplotlibでグラフを描画
  <body>
    <py-config>
      packages = ["matplotlib", "numpy"]
    </py-config>
    <py-script>
      import matplotlib.pyplot as plt
      import numpy as np

      plt.style.use("seaborn")
      x = np.linspace(0, 10, 100)
      y = 4 + 2 * np.sin(2 * x)
      fig, ax = plt.subplots()
      ax.plot(x, y, linewidth=2.0)
      ax.set(xlim=(0, 8), xticks=np.arange(1, 8),
             ylim=(0, 8), yticks=np.arange(1, 8))
      display(fig)
    </py-script>
  </body>

display()関数にfigオブジェクトを渡すと、このようにグラフが描画されます。

Matplotlibでグラフを描画
Matplotlibでグラフを描画

先ほどPandasのDataFrameの例ではHTMLが表示されましたが、Matplotlibの場合はグラフの画像が表示されます。グラフが描画されたHTMLを見てみると、以下のように画像がHTML中に埋め込まれています。

<div><img src="data:image/png;charset=utf-8;base64,iVBORw0KGgoAA...SuQmCC"></div>

実はdisplay()関数はテキスト、HTML以外にもさまざまなデータの描画に対応しています。以下のドキュメントを参照すると画像ファイル以外にも、PDF、SVG、JSONなどにも対応しているそうです。

PyScriptに組み込まれたライブラリ

PyScriptのpackagesには、他にどのようなサードパーティ製のライブラリを指定できるでしょうか?後述するPyScriptのベースとなっているPython環境Pyodideに組み込まれているライブラリが指定可能です。ライブラリの一覧は以下のページから参照できます。

一例として、よく名前を聞くライブラリとしては、以下のものが組み込まれています。

  • beautifulsoup4, bokeh, cryptography, geopandas, Jinja2, lxml, networkx, opencv-python, Pillow, python-dateutil, scikit-learn, scipy

Pure Pythonのライブラリを使用する

実は、PyScriptに組み込まれていないサードパーティ製のライブラリも使用できます。Pure Python(Pythonのみで実装された)のライブラリで、PyPIにwheelファイルが提供されているものが使用できます。packagesにライブラリの名前を指定すると、PyPIからwheelファイルをダウンロードしてPyScript上にインストールされます。Pure PythonではないライブラリをPyScriptで使用する方法については後述します。

ここでは例としてレーベンシュタイン距離を計算するlevenshtein-distanceというライブラリをインストールして使用してみます。packages = ["levenshtein-distance"]と指定していますが、このパッケージはPyScriptには組み込まれていません。

levenshtein.html - levenshtein-distanceパッケージをインストール
  <body>
    <py-config>
      packages = ["levenshtein-distance"]
    </py-config>

WebブラウザでHTMLファイルを開くときに、デベロッパーツールでネットワークの通信を確認します。すると、以下のようにパッケージのwheelファイルlevenshtein_distance-1.0.1-py3-none-any.whlがダウンロードされていることがわかります。このhttp://files.pythonhosted.org/から始まるURLは、PyPIのファイルを配布するURLと同じものです。つまり、PyPIから情報を取得して、適切なwheelファイルをダウンロードしてインストールしています。

wheelがダウンロードされている
wheelがダウンロードされている

コード全体は以下のような内容で、2つの文字列のレーベンシュタイン距離を計算して表示します。2つの入力フィールドから値を取り出して結果を返します。

levenshtein.html - 2つの単語のレーベンシュタイン距離を計算
  <body>
    <py-config>
      packages = ["levenshtein-distance"]
    </py-config>
    <input class="py-input" type="text" id="text1" />
    <input class="py-input" type="text" id="text2" />
    <button class="py-button" type="submit" py-click="leven()">Go</button>
    <div id="output"></div>
    <py-script>
      from levenshtein_distance import Levenshtein

      def leven():
          text1 = Element("text1").value
          text2 = Element("text2").value
          lev = Levenshtein(text1, text2)
          display(f"レーベンシュタイン距離: {lev.distance()}", target="output", append=False)
    </py-script>
  </body>
2つの文字列のレーベンシュタイン距離
2つの文字列のレーベンシュタイン距離

もう1つPure Pythonのパッケージを利用した例を紹介します。Janomeという日本語の形態素解析ライブラリは、Pure Pythonで書かれており日本語の辞書も内包しています。そのため、PyScript上でもそのまま動作します。

以下のコードでは、Janomeを使用して入力された日本語の分かち書き(単語に区切る処理)を実行しています。Janomeをインストールして起動するまでに少し時間がかかりますが、問題なくWebブラウザ上で動作します。想定通りの動作ではありますが、ブラウザ上でJanomeが動作することに驚きました。

janome.html - Janomeで日本語の形態素解析
  <body>
    <py-config>
      packages = ["Janome"]
    </py-config>
    <input class="py-input" type="text" id="text" size=40 />
    <button class="py-button" type="submit" py-click="wakati()">分かち書き</button>
    <div id="output"></div>
    <py-script>
      from janome.tokenizer import Tokenizer

      t = Tokenizer()
      def wakati():
          text = Element("text").value
          tokens = t.tokenize(text, wakati=True)
          display(" ".join(tokens), target="output")
    </py-script>
  </body>
ブラウザ上で形態素解析ができる
ブラウザ上で形態素解析ができる

RequestsでWeb API呼び出し

Pythonのサードパーティ製HTTPクライアントとしてRequestsがよく使われますが、<py-config>で読み込んでもそのままでは使用できません。PyScriptでは、Pyodide-HTTPというライブラリを使用してRequests(またはurllib.requestにパッチをあてることで、ライブラリが使えるようになります。

以下のコードではpyodide_http.patch_all()関数を実行することで、Reqeustsにパッチをあてています。そしてrequests.get()関数でスターウォーズAPIを呼び出して、JSON形式のレスポンスから映画のタイトル、公開日を取得しています。for文の中では、出演キャラクターのリストから最初の5件の情報を取得し、名前を出力しています。

requests.html - RequestsでスターウォーズAPIを呼び出す
  <body>
    <py-config>
      packages = ["requests", "pyodide-http"]
    </py-config>
    <py-script>
      import requests
      import pyodide_http

      pyodide_http.patch_all()  # Requestsにパッチをあてる
      response = requests.get("https://swapi.dev/api/films/1/")
      data = response.json()
      display(f"タイトル: {data['title']}")
      display(f"公開日: {data['release_date']}")
      for character_url in data["characters"][:5]:
          response = requests.get(character_url)
          display(response.json()["name"])
    </py-sctip>
  </body>

HTMLをWebブラウザで表示すると、以下のようにAPIから取得したスターウォーズの映画とキャラクターの情報が出力されます。

RequestsでAPIを呼び出し
RequestsでAPIを呼び出し

RequestsでのAPI呼び出しについては以下のドキュメントも参照してください。また、パッチを使用せずにpyodide.httpモジュールのpyfetch()関数を使用したHTTPアクセスも可能です。詳細は以下のドキュメントを参照してください。

PyScriptはどのように動いているのか

さて、PyScriptはどのようにWebブラウザ上で動いているのでしょうか? PyScriptは先ほどから名前が出ているPyodideWebAssemblyをベースに作られています。

WebAssembly - Webブラウザ上で動作する低レベル言語

WebAssembly(以下:Wasm)はWebブラウザ上で実行できるコードですが、JavaScriptとは異なり低レベルな言語であり、ネイティブに近いパフォーマンスで動作します。Wasmを直接書くというよりは、C/C++言語など他の言語で書いたプログラムをコンパイルし、Wasm上で動作させることが一般的です。RustやGo言語は標準でWasm用にコンパイルするオプションが存在するようです。

WebAssembly公式サイト
URL:https://webassembly.org/

Wasmが実際に使われている例で有名なものとしては、Google Meetの背景をぼかす機能があります。通常のビデオ画像をWebブラウザ上のWasmのコードで背景をぼかすことにより、JavaScriptより高速に動作しているそうです。また、今回調べるまで知りませんでしたが、Disney+やAmazon Prime Videoの動画配信クライアントにもWasmが使用されているそうです。

Pyodide - Webブラウザ上で動作するPython

PyodideはWebブラウザ上で動作するPythonの環境です。CPythonをWebAssemblyで動作するようにポーティングしたものです。Emscriptenを使用してCPythonのコードをWebAssembly用にコンパイルしています。

Pyodide公式サイト
URL:https://pyodide.org/

Pyodide単体でもWebブラウザ上でPythonは動作します。以下のHTMLファイルをWebブラウザで開くと、コンソールログにバージョン情報と現在日時が出力されます。

pyodide.html - PyodideをWebブラウザで実行
<html lang="ja">
  <head>
    <meta charset="utf-8"/>
    <script src="https://cdn.jsdelivr.net/pyodide/v0.23.0/full/pyodide.js"></script>
  </head>
  <body>
    コンソールログに結果を出力
    <script type="text/javascript">
      async function main(){
        let pyodide = await loadPyodide();
        console.log(pyodide.runPython(`
            import sys
            sys.version
        `));
        pyodide.runPython("import datetime; print(datetime.datetime.now())");
      }
      main();
    </script>
  </body>
</html>

コードを見ての通り、runPython()関数の中にPythonのコードを文字列で書き込む形式なので、少し書きにくいです。

PyScriptではPyodideをベースに、ブラウザにPythonコードを埋め込んで実行するための<py-script>タグなどを提供しています。他にも、display()関数などブラウザ上でのPythonの開発に便利な機能を提供しており、Pyodideよりもコードが書きやすくなっていると感じます。

ライブラリをPyodide用にビルドする

さて、自分が使いたいライブラリがPure PythonではなくC言語などを使用しており、PyScript(のベースのPyodide)に組み込まれていない場合はどうすればよいのでしょうか?その場合は、自分が使いたいライブラリをPyodide用にビルドする必要があります。

Pythonパッケージを作成する方法はPyodide 0.23では、out of treein treeの2種類の方法が用意されています。out of treeはPyodideのパッケージとは別にパッケージを作成する方法、in treeはPyodideの組み込みパッケージとして追加する方法です。パッケージを自身のプロジェクトのみで使用する場合はout of treeの方法で問題なさそうです。

ここではin treeでのパッケージの追加方法を簡単に紹介します。

  • Pyodideのリポジトリをcloneする
  • pip -e pyodide-buildコマンドでpyodide-buildをインストールする
  • Pyodide用のPythonをビルドする
  • 対象のパッケージをビルドするためのmeta.ymlファイルを作成する
    • ファイルはpackages/フォルダーの下にパッケージ名のフォルダーを作成して、その中に配置する
    • meta.ymlの中にはパッケージの情報、ソースコードの場所、ビルド時のオプションなどを記述する
  • pyodide build-recipes <package-name> --installコマンドで対象のパッケージをビルドする
  • 実際にパッケージが動作するかをWebブラウザで確認する

詳細な手順は以下のドキュメントを参照してください。

新しいパッケージを追加したい場合は、PyodideのリポジトリでPull Requestを作成します。たとえば、以下のPull Requestではmultidictという、辞書に似たライブラリが追加されています。

まとめ

以上でPyScriptについて解説は終了です。PyScriptがどういったもので、どういう機能があるのかイメージできましたでしょうか? 今回、この記事を執筆するにあたって、PyScriptのドキュメントを調べ直しましたが、思った以上にさまざまな機能が用意されており、可能性を感じました。

PyScriptをどのような用途で使うのかという点では、1つはWebブラウザだけで動作する簡単なアプリケーションを、HTML要素とPyScriptだけで作ってみるのも面白いのではと思いました。ジャンケンの例はかなりシンプルですが、もう少し表示やできることを追加すれば簡単な対話型のゲームが作成できそうです。

また、Python環境がPCになくても動作するため、プログラミング教育などにも向いていると感じました。テキストをHTMLで表示し、その下にPyScriptでサンプルコードを表示して実行するといった形式で、学習しやすくなるのではと思っています。筆者はPython Boot Campというイベントで講師をしていますが、PCで普段プログラミングをしていない人に、ターミナルでシェルと対話モードを行き来することはなかなか難しいなと感じています。前半はPyScriptを使用して、Webブラウザだけで完結する形にしたら、もしかしたら勉強しやすくなるのかも知れないと考えています。また、そうなれば、iPadやスマートフォンでも学習できるようになります。

筆者がパーソナリティをしているYouTubeライブでも、以前PyScriptについて扱ったことがあります。実際に動作している様子も見れるので、こちらも参考にしてみてください。

活発に開発が進んでいるPyScriptに、今後も注目していきたいと思います。

参考リンク

おすすめ記事

記事・ニュース一覧