鈴木たかのり

Warning:PyScriptは現在非常に活発に開発が進んでいるプロダクトのため、将来的にこの記事のサンプルコードが動かなくなる可能性があります。記事執筆時点では最新バージョンであるPyScript 2023.
PyScriptとは?
PyScriptは公式サイトに
まずは説明の前に、実際に動いているところを見てもらった方が早いと思います。以下はシンプルなPyScriptを含んだ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.
などの適当なファイル名で保存してWebブラウザで開きます。最初にロードなどが実行され少し時間がかかりますが、HTMLの中でPythonが実行されて現在日時が表示されます。

PyScriptのコードの基本
ここでは簡単にPyScriptのコードの書き方について説明します。とはいえ、ほとんど見たままです。
まず、<head>
タグの中でpyscript.
とpyscript.
を読み込みます。このpyscript.
がPyScriptの本体とも言えるもので、このJavaScriptファイルを読み込むことにより、HTMLの中でPythonのコードが実行されるようになります。
<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.
によってHTMLの中で使えるようになっているのが、PyScriptの基本的な機能となっています。
最後のprint()
関数の出力した文字列が画面上に表示されます。
<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氏によって発表されました。発表の時は会場でも
PyScriptはGitHub上でオープンに開発が進められています。メンテナーとしても現在はAnaconda社以外の方も参加しているようです。ライセンスもApache License 2.
- PyScriptのGitHubリポジトリ
- URL:https://
github. com/ pyscript/ pyscript
PyScriptでできること
最初に示したシンプルなコードだけでなく、PyScriptではさまざまなことができます。ここでは、サンプルコードを交えてPyScriptでどういうコードが記述でき、どのように動作するかについて説明します。
display()
関数での出力
先ほどprint()
関数で結果を出力した場合は、ターミナルのような表示になっていましたが、display()
関数を使用するとHTMLの中にそのまま埋め込まれます。なお、以降のコードでは<head>
部分などは共通なので省略しています。
<body>
<py-script>
from datetime import datetime
now = datetime.now()
display(f"今は{now:%Y年%m月%d日 %H:%M:%S}です。")
</py-script>
</body>
このHTMLをWebブラウザで表示すると以下のようになります。

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

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

display()
関数のリファレンスは以下から参照できます。
Element()
を使用したHTMLの書き換え
PyScriptではHTML中の要素を取得して、書き換えることも可能です。この機能を使うことでより柔軟にHTMLを書き換えられます。Element()
で指定したHTML要素を取得し、innerHTML
にHTML文字列を代入することで書き換えが可能です。innerText
に代入すると文字列として書き換えられます<>
はエスケープされます)。ここでは文字列として<b>
タグを指定することで、太字になっていることが確認できます。
<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
APIのリファレンスは以下から参照できます。
<py-repl>
タグによる対話モードの実行
PyScriptには<py-repl>
というタグが用意されており、このタグを使用するとPythonの対話モードがWebブラウザ上で動作します。
<body>
<py-repl></py-repl>
</body>
このコードをWebブラウザで表示すると以下のような表示になります。Jupyter NotebookやGoogle Colaboratoryを使ったことがある方は、似たような表示だと感じると思います。ただJupyter Notebookなどと異なるのは、Webブラウザの中だけで動作しているということです。PythonがインストールされていないPC、スマートフォン上のWebブラウザでも動作します。

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

auto-generate="true"
引数を指定すると、実行するごとに新しい対話モードの入力領域が表示されます。ますますJupyter Notebookっぽいです。
<body>
<py-repl auto-generate="true"></py-repl>
</body>
以下はPyScriptの対話モードでいくつかのコードを実行した例です。2番目の実行結果より、now
変数が保持されていることがわかります。また、sys.
によりPython 3.

<py-repl>
タグのリファレンスは以下から参照できます。
イベントの取得
ここまではHTMLを表示したときにPyScriptが実行され、その結果が画面に表示されるという例だけを扱っていました。PyScriptでは各種イベントをトリガーにして関数を実行することもできます。
以下のコードではbutton
タグにpy-click
という属性を指定してあります。py-click
属性には要素をクリックしたときのイベントを指定できます。この場合はget_
関数が実行されます。get_
関数の中では現在時刻を取得して、その結果をid="now"
の要素に表示します。
<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つのボタンにそれぞれ
<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.
)<py-script>
タグの中にあった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-script>
タグのsrc
属性に指定します。これで、同じように動作します。
<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から別のファイルを読み込むことができません。以下のようなエラーが表示されます。エラーメッセージには

この場合は、janken2.
とjanken.
が存在するディレクトリで、簡易的なHTTPサーバーを起動すると解決します。コマンドラインで以下のようにpython3 -m http.
を実行してHTTPサーバーを起動します。それから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ページ用のリポジトリから、タイムテーブル情報の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>
の中でpandas
とnumpy
をパッケージとして指定しています。そして<py-script>
の中でこの2つのライブラリを使用してPandasのDataFrameを生成しています。
<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としてそのまま出力するためです。

Matplotlibでグラフ描画
他にもグラフ描画で使われるMatplotlibにも対応しています。以下のコードのように、通常の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
オブジェクトを渡すと、このようにグラフが描画されます。

先ほど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
には、他にどのようなサードパーティ製のライブラリを指定できるでしょうか?
一例として、よく名前を聞くライブラリとしては、以下のものが組み込まれています。
- beautifulsoup4, bokeh, cryptography, geopandas, Jinja2, lxml, networkx, opencv-python, Pillow, python-dateutil, scikit-learn, scipy
Pure Pythonのライブラリを使用する
実は、PyScriptに組み込まれていないサードパーティ製のライブラリも使用できます。Pure Pythonpackages
にライブラリの名前を指定すると、PyPIからwheelファイルをダウンロードしてPyScript上にインストールされます。Pure PythonではないライブラリをPyScriptで使用する方法については後述します。
ここでは例としてレーベンシュタイン距離を計算するlevenshtein-distanceというライブラリをインストールして使用してみます。packages = ["levenshtein-distance"]
と指定していますが、このパッケージはPyScriptには組み込まれていません。
<body>
<py-config>
packages = ["levenshtein-distance"]
</py-config>
WebブラウザでHTMLファイルを開くときに、デベロッパーツールでネットワークの通信を確認します。すると、以下のようにパッケージのwheelファイルlevenshtein_
)http://
から始まるURLは、PyPIのファイルを配布するURLと同じものです。つまり、PyPIから情報を取得して、適切なwheelファイルをダウンロードしてインストールしています。

コード全体は以下のような内容で、2つの文字列のレーベンシュタイン距離を計算して表示します。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>

もう1つPure Pythonのパッケージを利用した例を紹介します。Janomeという日本語の形態素解析ライブラリは、Pure Pythonで書かれており日本語の辞書も内包しています。そのため、PyScript上でもそのまま動作します。
以下のコードでは、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
以下のコードではpyodide_
関数を実行することで、Reqeustsにパッチをあてています。そしてrequests.
関数でスターウォーズAPIを呼び出して、JSON形式のレスポンスから映画のタイトル、公開日を取得しています。for
文の中では、出演キャラクターのリストから最初の5件の情報を取得し、名前を出力しています。
<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呼び出しについては以下のドキュメントも参照してください。また、パッチを使用せずにpyodide.
モジュールのpyfetch()
関数を使用したHTTPアクセスも可能です。詳細は以下のドキュメントを参照してください。
- Calling an API using Requests - PyScript documentation
- How to make HTTP requests using PyScript, in pure Python - PyScript documentation
PyScriptはどのように動いているのか
さて、PyScriptはどのようにWebブラウザ上で動いているのでしょうか? PyScriptは先ほどから名前が出ているPyodideとWebAssemblyをベースに作られています。
WebAssembly - Webブラウザ上で動作する低レベル言語
WebAssembly
- WebAssembly公式サイト
- URL:https://
webassembly. org/
Wasmが実際に使われている例で有名なものとしては、Google Meetの背景をぼかす機能があります。通常のビデオ画像をWebブラウザ上のWasmのコードで背景をぼかすことにより、JavaScriptより高速に動作しているそうです。また、今回調べるまで知りませんでしたが、Disney+やAmazon Prime Videoの動画配信クライアントにもWasmが使用されているそうです。
- Background Features in Google Meet, Powered by Web ML – Google AI Blog
- ディズニー、Disney+の動画配信クライアントにWebAssemblyを採用。2019年春に開発開始 - Publickey
Pyodide - Webブラウザ上で動作するPython
PyodideはWebブラウザ上で動作するPythonの環境です。CPythonをWebAssemblyで動作するようにポーティングしたものです。Emscriptenを使用してCPythonのコードをWebAssembly用にコンパイルしています。
- Pyodide公式サイト
- URL:https://
pyodide. org/
Pyodide単体でもWebブラウザ上でPythonは動作します。以下のHTMLファイルを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
Pythonパッケージを作成する方法はPyodide 0.
ここではin treeでのパッケージの追加方法を簡単に紹介します。
- Pyodideのリポジトリをcloneする
pip -e pyodide-build
コマンドでpyodide-buildをインストールする- Pyodide用のPythonをビルドする
- 対象のパッケージをビルドするための
meta.
ファイルを作成するyml - ファイルは
packages/
フォルダーの下にパッケージ名のフォルダーを作成して、その中に配置する meta.
の中にはパッケージの情報、ソースコードの場所、ビルド時のオプションなどを記述するyml
- ファイルは
pyodide build-recipes <package-name> --install
コマンドで対象のパッケージをビルドする- 実際にパッケージが動作するかをWebブラウザで確認する
詳細な手順は以下のドキュメントを参照してください。
- Creating a Pyodide package - Version 0.
23. 0 - Building and testing Python packages out of tree - Version 0.
23. 0
新しいパッケージを追加したい場合は、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に、今後も注目していきたいと思います。