「喰う・書く・逃げる」に棲む処

 動物に関するデータ分析者のブログです

PyScriptで動的Webページもどきを作る

今、激アツのPyScriptで遊んでみました。

pyscript.net

PyScriptとは?

いろんな情報が飛び交っているのでわざわざ説明するまでもないですが、サーバーを立てずにHTMLだけでPythonが動かせるという素晴らしいものです。 JapaScriptと比較している人もいますが、一番のメリットとしてはサーバーレスでPythonの充実したライブラリーの恩恵を受けられることではないでしょうか? 全てのライブラリを使えるわけではないですが、それでも素晴らしいと思います。

Pyodideをベースに開発されているようなので使えるライブラリはPyodideに準拠している以下のライブラリのみのようです。

pyodide.org

細かい仕組みについてはいろいろな解説がありますのでそれらを参考にしてください。

使い方

基本

HTMLのヘッダーに以下の二行を追加します。

<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>

そして<body>の中に

<py-script>
print("Hello from PyScript")
</py-script>

こんな感じで<py-script>タグで挟んでpythonコードを書きます。

ちなみにPythonコードはインデントもPythonコードとして認識しますので注意してください。

すると、Hello from PyScriptと書かれたシンプルなWebページが出来上がります。

もう少し、複雑なWebページにしたい場合はHTMLの中にIDを付けた<div>タグを用意します。

<div id="hello"></div>

そして

<py-script>
pyscript.write("hello", "Hello from Python")
</py-script>

とすると、”hello”IDの<div>タグの中に出力結果が埋め込まれます。Matplotlibで出力した図でも大丈夫です。

ライブラリのインポート

ヘッダーの中に以下のようにかきます。

<py-env>
    - numpy
    - matplotlib
    - paths:
        - ./mymodule.py
</py-env>

こうすることで、NumpyMatplotlibmymodule.pyで書いた自作プログラムを使う環境が整えられます。

後は通常のPythonと同じようにインポートするだけです。

HTMLの<input>タグと連携する

ここからが、この記事で最も書きたかった本題です。

<input>タグの入力値と連携して出力結果を動的に変更する方法について書きます。

例えば、こんな感じで0から10まで選択できるIDが"numbers"のスライダー、IDが"btn"ボタンを用意しておきます。

ついでにIDがnumの<div>タグも用意しておきます。

<input id="numbers" type="range" min="0" max="10" step="1">
<br>
<input type="button" value="click_me" id="btn">
<div id="num"></div>

続いて、<py-script>タグの中に

doc_number=document.getElementById('numbers')

と書きます。

すると、Pythonの変数doc_numberにスライダーの値が入ります(コードがJavaScriptぽい)。

そして、以下のようにコールバックファンクションとaddEventListenerを書いてあげると上で定義したIDが"num"の<div>タグにスライダーの値が埋め込まれます。

import pyodide

#コールバックファンクション
def on_click(e): #使わないとしても変数は必須
    doc2=document.getElementById('num')
    doc2.innerHTML = doc_number.value       

btn_element = document.getElementById('hello')
btn_element.addEventListener('click', pyodide.create_proxy(on_click))

スライダーを動かしてクリックボタンを押すと数値が変化します。

グラフを動的に表示してみる

続いてグラフを表示してみます。

まず、グラフを出力する関数を書きます。

import numpy as np
import matplotlib.pyplot as plt

def pyplots(val):
    x=np.arange(1,11)
    y=x**val
    fig,ax = plt.subplots()
    ax.plot(x,y)
    return fig

変数に応じて指数関数のようになったり直線のようになったりするグラフです。

以下のコードを<py-script>タグの中の後ろのほうに付け足すと、Webページに上のグラフが表示されるようになります。

pyscript.write("plt",pyplots(float(doc_number.value)))

しかし、このままでは初めに出力した図を表示しているだけで変化させることができません。

そこで、非同期処理のループを追加します。

async def tick():
    while True:
        try:
            doc_number=document.getElementById('numbers')
            pyscript.write("num",doc_number.value)
            pyscript.write("plt", pyplots(float(doc_number.value)))
            await asyncio.sleep(1)
                
        except Exception as e:
            break

pyscript.run_until_complete(tick())

非同期処理はこちらを参考にさせてもらいました。

qiita.com

スライダーを動かすと同時にグラフを動的に変化させることができます。

他に参考になりそうなサイト・補足

pyscript.net

ここに行くといろんなデモを見ることができます。参考になるかもしれないのでお勧めです。

あと、<py-inputbox>とか、インポートタブを生成するような機能もPyScriptは持っているようですが、現時点ではPyodideとHTMLを組み合わせるほうが書きやすそうです。


最後に上で書いた全コードを載せておきます

<!DOCTYPE html>
<html lang="jp">
<head>
    <meta charset="utf-8">
    <link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
    <script defer src="https://pyscript.net/alpha/pyscript.js"></script>
    <title>PyScript-Demo</title>
    <py-env>
        - numpy
        - matplotlib    
    </py-env>
    
</head>
<body>
    <div id="hello"></div>
    <input id="numbers" type="range" min="0" max="10" step="1">
    <br>
    <input type="button" value="click_me" id="btn">
    <div id="num"></div>
    <div id="plt"></div>
    <py-script>
    import pyodide
    import numpy as np
    import matplotlib.pyplot as plt

    pyscript.write("hello","Hello to div tag")
    print("Hello from PyScript")

    #グラフ
    def pyplots(val):
        x=np.arange(1,11)
        y=x**val
        fig,ax = plt.subplots()
        ax.plot(x,y)
        return fig

    doc_number=document.getElementById('numbers')
    def on_click(e):
        doc2=document.getElementById('num')
        doc2.innerHTML = doc_number.value
        #doc2.innerHTML = pyplots(float(doc_number.value))
            
    btn_element = document.getElementById('btn')
    btn_element.addEventListener('click', pyodide.create_proxy(on_click))

    pyscript.write("plt",pyplots(float(doc_number.value)))
    
    async def tick():
        while True:
            try:
                doc_number=document.getElementById('numbers')
                pyscript.write("num",doc_number.value)
                pyscript.write("plt", pyplots(float(doc_number.value)))
                await asyncio.sleep(1)
                
            except Exception as e:
                break

    pyscript.run_until_complete(tick())
    </py-script>
</body>
</html>