HTML内にPythonコードを書いて実行できるPyScriptで遊んでみた!
こんにちは👋
突然ですがみなさん、PyScriptというフレームワークをご存知でしょうか。
リンク先の記事などを読むに、どうやら、PyScriptというのはJavaScriptのようにHTML内にPythonコードを直接書き、その処理を実行できるというフレームワークらしいです。
今回はそんなPyScriptを使って少し遊んでみたいと思います。
PyScriptのインストール方法
なんとこのPyScript、インストールを必要としません。HTMLファイルのhead
タグ内に以下を追加するだけですぐ使用できます。
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
ちなみに、公式サイトからpyscript.css
とpyscript.js
をダウンロードして読み込むことも可能です。
遊んでみる
文字の出力
まずは文字の出力を行いたいと思います。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
<title>PyScript Playground</title>
</head>
<body>
<main class="py-5 grid gap-y-4 grid-cols-1 place-items-center">
<p id="title" class="text-center text-7xl"></p>
</div>
</main>
<py-script output="title">
print('I love PyScript')
</py-script>
</body>
</html>
実際のページが以下になります。
ちゃんとI love PyScript
の文字が表示されています。
body
タグの中を見ながら少し解説したいと思います。
<body>
<main class="py-5 grid gap-y-4 grid-cols-1 place-items-center">
<p id="title" class="text-center text-7xl"></p>
</div>
</main>
<py-script output="title">
print('I love PyScript')
</py-script>
</body>
まず、Pythonのコードを書いているのはpy-script
タグで囲まれた部分になります。この中は完全にPythonのコードになるので、当然ですがインデントもしっかりしなければエラーになります。また、py-script
タグのoutput
で指定されているのは、Pythonコードの実行内容をhtmlのどの部分に表示するかを指定しています。html要素のid
の値を使用して任意の要素にPythonコードの実行結果を渡す事ができます。今回の例では、<py-script output="title">...</py-script>
と書かれてるので、<p id="title" class="text-center text-7xl"></p>
にPythonコードの実行結果であるprint('I love PyScript')
が渡されています。
ちなみに、pyscript.write(idの値, 渡したい値)
としても同じ結果を得ることができます。その場合は以下のようになります。
<body>
<main class="py-5 grid gap-y-4 grid-cols-1 place-items-center">
<p id="title" class="text-center text-7xl"></p>
</div>
</main>
<py-script output="title">
title = 'I love PyScript'
pyscript.write('title', title)
</py-script>
</body>
そして、スタイリングについてですが、どうやらTailwind CSSで使用されているクラス名がそのまま使用できるようです。ただ、これについてはどこかに書いてあったわけでもなく、自分で気付いただけなので、もっと調べる必要があると思います。
Pythonの標準ライブラリを使う
Pythonコードなので当然ですが標準ライブラリを使うことができます。
それでは、コードを以下のように変更して、使用されているPythonのバージョンを確認してみましょう。Pythonのバージョン確認にはplatform
を使用したいと思います。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
<title>PyScript Playground</title>
</head>
<body>
<main class="py-5 grid gap-y-4 grid-cols-1 place-items-center">
<p id="title" class="text-center text-7xl"></p>
</main>
<py-script output="title">
import platform # 追加
print('I love PyScript')
print('Python version: {}'.format(platform.python_version())) # 追加
</py-script>
</body>
</html>
はい、ちゃんとPythonのバージョンが確認できました。
コードの変更を加えたpy-script
の中身について解説したいと思います。
<py-script output="title">
import platform # 追加
print('I love PyScript')
print('Python version: {}'.format(platform.python_version())) # 追加
</py-script>
ライブラリのインポートはpy-script
の中で行います。そしてその後は普段どおりに処理を記述するだけです。本当にPythonのコードをそのまま書けるのでとても楽です。
外部ライブラリを使う
Pythonの強みといえば優秀な外部ライブラリの存在だと思います。PyScriptでも一部ではありますが、Pythonの外部ライブラリを使用することができます。使用可能なライブラリは以下のPyodideのサイトで確認できるようになっていて、numpyやpandas、matplotlib、Pillow、scikit-learnなどが使用可能になっています。
それでは、今回はmatplotlibを使用してグラフの表示をしてみたいと思います。
コードは以下のように変更します。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
<!-- 追加 ここから -->
<py-env>
- numpy
- matplotlib
</py-env>
<!-- 追加 ここまで -->
<title>PyScript Playground</title>
</head>
<body>
<main class="py-5 grid gap-y-4 grid-cols-1 place-items-center">
<p id="title" class="text-center"></p>
<!-- 追加 ここから -->
<py-button id="graph-button" label="グラフを表示"></py-button>
<div id="graph-container"></div>
<!-- 追加 ここまで -->
</main>
<py-script output="title">
import platform
print('I love PyScript')
print('Python version: {}'.format(platform.python_version()))
</py-script>
<!-- 追加 ここから -->
<py-script>
import numpy as np
from numpy import pi, sin, cos
import matplotlib.pyplot as plt
graph_button = Element('graph-button')
def display_graph(*ags, **kwgs):
t = np.arange(0,2*pi,0.01)
x = 16*sin(t)**3
y = 13*cos(t)-5*cos(2*t)-2*cos(3*t)-cos(4*t)
fig, ax = plt.subplots()
ax.fill_between(x, y, facecolor='red', alpha=0.5)
pyscript.write('graph-container', fig)
graph_button.element.onclick = display_graph
</py-script>
<!-- 追加 ここまで -->
</body>
</html>
実際に動かすと以下のようになります。
グラフを表示
というボタンをクリックすると素敵なグラフが表示されます。では、追加部分のコードを解説したいと思います。
<py-env>
- numpy
- matplotlib
</py-env>
まず、head
タグ内のこの部分ですが、py-env
タグ内で、使用したい外部ライブラリ名を記述します。これにより、py-script
タグ内で任意のライブラリをインポート可能になります。
<py-button id="graph-button" label="グラフを表示"></py-button>
<div id="graph-container"></div>
次に上記の部分、py-button
という見慣れないタグがありますが、PyScriptにはVisual component tag
というすでにボタンや入力ボックスの見た目が整えられたものが用意されており、それらを使用することで簡単に実装が可能になっていて、py-button
ではボタンコンポーネントの実装を行っています。以下のリンクからVisual component tag
の一覧を見ることができます。(まだ種類は少ないですが...)
そして、div
タグのid="graph-container"
の部分を記述し、このあと実行するPythonコードから得られたグラフを表示するエリアの用意をしています。
<py-script>
import numpy as np
from numpy import pi, sin, cos
import matplotlib.pyplot as plt
graph_button = Element('graph-button')
def display_graph(*ags, **kwgs):
t = np.arange(0,2*pi,0.01)
x = 16*sin(t)**3
y = 13*cos(t)-5*cos(2*t)-2*cos(3*t)-cos(4*t)
fig, ax = plt.subplots()
ax.fill_between(x, y, facecolor='red', alpha=0.5)
pyscript.write('graph-container', fig)
graph_button.element.onclick = display_graph
</py-script>
py-script
タグ内ではまず、head
タグ内で記述したライブラリのインポートを行います。graph_button = Element('graph-button')
でボタン要素の取得を行い、graph_button.element.onclick = display_graph
でボタンクリック時にdisplay_graph
関数が実行されるようになっています。この部分はまさにJavaScriptで行えるDOM操作って感じで感動しました。display_graph
の中身は普通にpltでグラフを作成し、pyscript.write
で指定のdiv
タグ内に描写しているだけなので、特に説明不要かと思いますが、巷で話題の三角関数を使用して素敵なハート型のグラフを作成している点は注目ポイントかもしれません。
scikit-learnでデータを見る
Pythonを使用している方の中にはデータ分析をメインとしている方も多いのではないでしょうか。
というわけで、scikit-learnで用意されているデータセットから王道のThe Iris Datasetを使用してデータの中身の確認やグラフでデータの特徴を見てみたいと思います。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
<py-env>
- numpy
- matplotlib
# 追加 ここから
- scikit-learn
- pandas
# 追加ここまで
</py-env>
<title>PyScript Playground</title>
</head>
<body>
<main class="py-5 grid gap-y-4 grid-cols-1 place-items-center">
<p id="title" class="text-center"></p>
<py-button id="graph-button" label="グラフを表示"></py-button>
<div id="graph-container"></div>
<!-- 追加 ここから -->
<py-button id="iris-button" label="Irisデータセットを分析"></py-button>
<div id="iris-container">
<p id="data-description"></p>
<div id="plot-area"></div>
</div>
<!-- 追加 ここまで -->
</main>
<py-script output="title">
import platform
print('I love PyScript')
print('Python version: {}'.format(platform.python_version()))
</py-script>
<py-script>
import numpy as np
from numpy import pi, sin, cos
import matplotlib.pyplot as plt
graph_button = Element('graph-button')
def display_graph(*ags, **kwgs):
t = np.arange(0,2*pi,0.01)
x = 16*sin(t)**3
y = 13*cos(t)-5*cos(2*t)-2*cos(3*t)-cos(4*t)
fig, ax = plt.subplots()
ax.fill_between(x, y, facecolor='red', alpha=0.5)
pyscript.write('graph-container', fig)
graph_button.element.onclick = display_graph
</py-script>
<!-- 追加 ここから -->
<py-script>
from sklearn import datasets
import pandas as pd
iris_button = Element('iris-button')
dataset = datasets.load_iris()
def display_irisdata(*ags, **kwgs):
df = pd.DataFrame(dataset.data, columns=dataset.feature_names)
pyscript.write('data-description', df.head())
fig, ax = plt.subplots()
ax.scatter(x=dataset.data[dataset.target==0, 0],
y=dataset.data[dataset.target==0, 1],
label=dataset.target_names[0],
c='blue')
ax.scatter(x=dataset.data[dataset.target==1, 0],
y=dataset.data[dataset.target==1, 1],
label=dataset.target_names[1],
c='red')
ax.scatter(x=dataset.data[dataset.target==2, 0],
y=dataset.data[dataset.target==2, 1],
label=dataset.target_names[2],
c='green')
ax.legend(loc='best', fontsize=14)
ax.set_title('Iris SepalLength / SepalWidth', size=16)
ax.set_xlabel(dataset.feature_names[0], size=14)
ax.set_ylabel(dataset.feature_names[1], size=14)
pyscript.write('plot-area', fig)
iris_button.element.onclick = display_irisdata
</py-script>
<!-- 追加 ここまで -->
</body>
</html>
今までの説明と同じことをしているので、細かい説明は不要かと思いますが、簡単に流れを説明すると、py-env
タグ内で使用する外部ライブラリを追加、main
タグ内にPythonコードの処理結果を表示するエリアを追加、py-script
タグ内でIrisデータセットのロードから、データを表示しやすいようpandas.DataFrame
への変換、アヤメの種類とがくの長さ、幅の関係を散布図でプロット、その結果をhtmlに描写という流れになっています。pandas.DataFrame
の表示はJupyter Labなどと違って少し見にくい感じがありますが、このようなデータ分析のプログラムがHTML内に記述できるようになるのはなかなか夢がありますね。
PythonコードをモジュールとしてインポートしてHTML内で使用する
HTMLってコードが長くなりがちで、スタイリングなんかも合わせるとごちゃごちゃ度が増して嫌だ。という方も多いのではないでしょうか。自分もHTMLファイルとにらめっこするのはあまり好きではありません。そんなHTMLファイル内にPythonコードなんて書いていたら余計複雑になる...と頭を抱えている方に朗報です。なんとこのPyScript、Pythonコードは.py
ファイルとして記述しておき、それをHTML内でモジュールとしてインポートして使用できます。
まずはディレクトリ構成を以下のようにします。
.
├── index.html
└── modules
├── create_graph.py
└── create_irisdata.py
py-script
内で記述していたdisplay_graph
やdisplay_irisdata
について、それぞれグラフ作成処理の部分を切り出し、create_graph.py
、create_irisdata.py
内に以下のように記述します。
import numpy as np
from numpy import pi, sin, cos
import matplotlib.pyplot as plt
def create_graph(*ags, **kwgs):
t = np.arange(0,2*pi,0.01)
x = 16*sin(t)**3
y = 13*cos(t)-5*cos(2*t)-2*cos(3*t)-cos(4*t)
fig, ax = plt.subplots()
ax.fill_between(x, y, facecolor='red', alpha=0.5)
return fig
from sklearn import datasets
import pandas as pd
import matplotlib.pyplot as plt
dataset = datasets.load_iris()
def create_irisdata(*ags, **kwgs):
df = pd.DataFrame(dataset.data, columns=dataset.feature_names)
fig, ax = plt.subplots()
ax.scatter(x=dataset.data[dataset.target==0, 0],
y=dataset.data[dataset.target==0, 1],
label=dataset.target_names[0],
c='blue')
ax.scatter(x=dataset.data[dataset.target==1, 0],
y=dataset.data[dataset.target==1, 1],
label=dataset.target_names[1],
c='red')
ax.scatter(x=dataset.data[dataset.target==2, 0],
y=dataset.data[dataset.target==2, 1],
label=dataset.target_names[2],
c='green')
ax.legend(loc='best', fontsize=14)
ax.set_title('Iris SepalLength / SepalWidth', size=16)
ax.set_xlabel(dataset.feature_names[0], size=14)
ax.set_ylabel(dataset.feature_names[1], size=14)
return df, fig
続いて、index.html
ファイルを以下のように変更します。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
<py-env>
- numpy
- matplotlib
- scikit-learn
- pandas
- paths:
- ./modules/create_graph.py
- ./modules/create_irisdata.py
</py-env>
<title>PyScript Playground</title>
</head>
<body>
<main class="py-5 grid gap-y-4 grid-cols-1 place-items-center">
<p id="title" class="text-center"></p>
<py-button id="graph-button" label="グラフを表示"></py-button>
<div id="graph-container"></div>
<py-button id="iris-button" label="Irisデータセットを分析"></py-button>
<div id="iris-container">
<p id="data-description"></p>
<div id="plot-area"></div>
</div>
</main>
<py-script output="title">
import platform
print('I love PyScript')
print('Python version: {}'.format(platform.python_version()))
</py-script>
<py-script>
from create_graph import create_graph
graph_button = Element('graph-button')
def display_graph(*ags, **kwgs):
fig = create_graph()
pyscript.write('graph-container', fig)
graph_button.element.onclick = display_graph
</py-script>
<py-script>
from create_irisdata import create_irisdata
iris_button = Element('iris-button')
def display_irisdata(*ags, **kwgs):
df, fig = create_irisdata()
pyscript.write('data-description', df.head())
pyscript.write('plot-area', fig)
iris_button.element.onclick = display_irisdata
</py-script>
</body>
</html>
py-script
タグの中が結構スッキリした気がすると思います。それでは、index.html
についてコードの解説をしたいと思います。
<py-env>
- numpy
- matplotlib
- scikit-learn
- pandas
- paths:
- ./modules/create_graph.py
- ./modules/create_irisdata.py
</py-env>
まず、別ファイルのPythonコードを使用するには、py-env
タグ内で- paths:
として、それ以降に使用したいPythonファイルのパスを指定します(インデントに注意)。そして、これも重要なのですが、各Pythonファイル内で使用されている外部ライブラリも全て記述する必要があります。
<py-script output="title">
import platform
print('I love PyScript')
print('Python version: {}'.format(platform.python_version()))
</py-script>
<py-script>
from create_graph import create_graph
graph_button = Element('graph-button')
def display_graph(*ags, **kwgs):
fig = create_graph()
pyscript.write('graph-container', fig)
graph_button.element.onclick = display_graph
</py-script>
<py-script>
from create_irisdata import create_irisdata
iris_button = Element('iris-button')
def display_irisdata(*ags, **kwgs):
df, fig = create_irisdata()
pyscript.write('data-description', df.head())
pyscript.write('plot-area', fig)
iris_button.element.onclick = display_irisdata
</py-script>
あとはいつも通りにpy-script
タグ内で各モジュールをインポートし、任意の処理を書いていくだけになります。
これでHTMLファイルがPythonコードでグチャグチャになるのが少しは回避できるのかなと思います。それと、これはPyScriptが発表されて間もないことも影響していますが、HTMLファイルでPythonコードを書いていると自動補完や自動整形が効かないので.py
ファイル内で記述することで快適にコーディングすることができます。記事の中でも言及したようにpy-script
タグは完全にPythonコードなので、コメントアウトも#
を使用しますし、インデントにも注意しなければならないので、.py
ファイルでコードが書けるのはとても楽でした。
まとめ
さて、ここまでPyScriptで遊んできましたが、実際のところ、現状のPyScriptを使用したページは表示速度がかなり遅く(今回のページで読み込みに10秒ほどかかる)、対応している外部ライブラリもOpenCVやseabornがないなどまだまだ本番環境での使用は遠いかなという感じもします。ただ、Pythonの優秀なライブラリの恩恵をHTML形式で簡単に色々な人に届けられるようになることを考えると、今後のPyScriptはかなり楽しみだなと思いました。
それでは、最後に今回使用したファイルや公式サイトのサンプルなどを紹介して終わりたいと思います。
使用ファイルや公式サイトなどの紹介
- 今回使用したファイル
ファイルをダウンロードしてVSCodeのLive Serverなどで簡単にページを見ることができます。
- PyScript公式サイト
- PyScriptサンプルページ集
PyScriptを使用して作成された様々なデモページを見ることができます。
- PyScriptのGitHub
docsの中などに、公式サイトなどに記載のない使用方法などについて記述されていたりするので、より深くPyScriptを知りたい方は見てみることをオススメします。
以上、PyScriptで遊んでみた話でした。
最後までお読みいただきありがとうございました。
Discussion