最近話題のPyScriptを使ってみる

# 初めに〜PyScriptとは〜
最近、**PyScript**というものが少しホットな話題になっているようです。

これまでHTMLの中に書くことができるのは**JavaScript**だけだったのですが、PyScriptを用いると**PythonコードをHTMLの中に書いて、実行することができます。** 
例えば入力されたデータを用いてグラフを描画するといったことは、これまでJavaScriptでしかできませんでしたが、これからはPythonを使うことでも可能になるのです！

2022年4月末に**Anaconda**から発表されたPyScriptですが、当記事執筆時点（2022年6月）で公開されているのは**アルファ版（パイロット版）** です。
まだまだ機能が足りないところもありますが、今後の開発が期待されます。

:::note warn
本記事執筆時点（2022年6月）で、PyScriptはアルファ版です。本番環境での使用はお控えください。
公式も「本番環境での使用は推奨しません」と発表しています。
:::

# 開発環境
この記事では以下の環境で開発を行っています。
 - MacOS Monterey version 12.4
 - ブラウザ：Safari version 15.5
（エディタはCotEditorを使うことが多いです。同士いますか...）

# 基本的な使い方
``head``タグに``<script defer src="https://pyscript.net/alpha/pyscript.js"></script>``を書いてやれば使えるようになります。
[PyScriptのページ](https://pyscript.net)からローカルにダウンロードして読み込んでも良いでしょう。

PyScript用のcssも準備されています。
そちらは``<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css"/>``から読み込めます。

```index.html
<!DOCTYPE html>
<html>
 <head>
  <link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css"/>
  <script defer src="https://pyscript.net/alpha/pyscript.js"></script>
 </head>
</html>
```

コードは``<py-script>``タグで囲んだ部分に書きます。
インデントをちゃんと書かないとエラーになるのはいつものpythonと同じですので注意が必要です。
```index.html
...
 <body>
 <py-script>
print("Hello, world!")
 </py-script>
 </body>
...
```
Loading runtime...というちょっとかっこいい画面
![スクリーンショット 2022-06-29 14.08.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611822/f8aca2e3-dd66-614b-06ac-5c1d37648a50.png "Loading runtime...というちょっとかっこいい画面")
左上にちゃんと``Hello, World!``。
![スクリーンショット 2022-06-29 14.09.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611822/5bac7b64-7cbb-8a8c-303a-053991bc0113.png)


``f-string``もちゃんと使うことができます。ただし、``<h1>``タグなどで装飾するのはできないようです。

```index.html
...
  <py-script>
My_name = "Rj.Chiba"
print("Hello, " + My_name)
print(f"Hello, {My_name}")
print("<h1>hogehoge</h1>")
  </py-script>

  <h1>
   <py-script>
print("hogehoge")
   </py-script>
  </h1>
...
```
![スクリーンショット 2022-06-29 15.07.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611822/3dda927e-e78b-9b82-d7db-b15ecd707d34.png)
これはPyScriptで表示している文字が``div``タグの中に書かれているため。

この場合は、表示させたい場所に``p``タグなどをあらかじめ作っておいて、その``id``を``<py-script output=[id]>``と渡してあげる。

```index.html
...
<body>
 <p id="title" style="font-size:20px;" align="center"></p>
 <py-script output="title">
print("Hello, World!")
 </py-script>
</body>
...
```
![スクリーンショット 2022-06-29 15.17.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611822/accf09c2-0148-d0af-2717-a3369b77334a.png)

なお、``pyscript.write(id名,渡す値)``としても同じ結果が得られます。
でも``output=[id]``は必要なんですよね...
```index.html
<body>
 <p id="title" style="font-size:20px;" align="center"></p>
 <py-script output="title">
pyscript.write("title","Hello Rj.Chiba!")
 </py-script>
</body>
```

対話的にスクリプトを実行することもできます。
そのために使うのが`<py-repl>`タグ。これを書いておくだけでスクリプトを入力し、実行することができます。

```index.html
...
<body>
  <py-repl>
  </py-repl>
</body>
...
```
なにも書かないと、もちろん対話シェルしか表示されない。
![スクリーンショット 2022-06-29 16.02.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611822/179fb307-adcd-d56d-799c-bb2c0d603d79.png)

実行してほしいスクリプトを最初から書いておくこともできます。
```index.html
...
<body>
  <py-repl>
print(1+1)
print(1+2)
  </py-repl>
</body>
...
```
![スクリーンショット 2022-06-29 16.05.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611822/6aacc402-c048-45ee-4b02-a5b6f03af8c6.png)


# 標準ライブラリも使いたい
pythonの良いところはやはりライブラリが充実している点。
PyScriptでは標準ライブラリを使用することができます。
まずはPythonのバージョンを表示させてみましょう。

普段pythonを使っている時と同様に、`platform`をimportして、`platform.python_version()`をprintします。

```index.html
<body>
 <p id="title" style="font-size:20px;" align="center"></p>
 <py-script output="title">
import platform
print(f"Python Version: {platform.python_version()}")
 </py-script>
</body>
```
![スクリーンショット 2022-06-29 15.27.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611822/3e1a5b34-32cc-58c9-8d4d-67f02505535e.png)
python 3.10が入っているんですね。

# 外部ライブラリも使いたい！
データなどを扱っている人で、`numpy`や`pandas`、`scikit-learn`あたりを使いたい人も多いのではないでしょうか。ここではそれらの外部ライブラリを読み込んで、データを出力してみたいと思います。

```index.html
<!DOCTYPE html>
<html>
  <head>
    <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>
  </head>
  <body>
    <div id="canvas" align="center"></div>
	  
    <py-script output="canvas">
import numpy as np
from matplotlib import pyplot as plt

fig = plt.figure()

x = np.linspace(0,10,1000)
y = np.sin(x)+np.cos(x*1.1)

plt.plot(x,y,c="r",lw=1,label=r"$y=\sin x+\cos(1.1x)$")
plt.grid()
plt.legend()
plt
    </py-script>
  </body>
</html>
```
![スクリーンショット 2022-06-29 15.34.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611822/30accfc7-147e-3468-c289-14f194d67311.png)

外部ライブラリを読み込むためには、`head`タグ内に`py-env`タグを作って、その中でインポートしたいライブラリを宣言します。
ここでは`numpy` と`matplotlib`を読み込んでいます。

```html
...
<head>
  <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>
</head>
...
```

グラフを表示させる方法は、普段のpythonコードとほとんど変わりません。

```html
...
  <div id="canvas" align="center"></div>	
  <py-script output="canvas">
# ライブラリをインポート
import numpy as np
from matplotlib import pyplot as plt

fig = plt.figure()

# データを作る
x = np.linspace(0,10,1000)
y = np.sin(x)+np.cos(x*1.1)

# プロット
plt.plot(x,y,c="r",lw=1,label=r"$y=\sin x+\cos(1.1x)$")
plt.grid()
plt.legend()
plt #いつもと違うところ！
  </py-script>
...
```

唯一違う点は最終行の`plt`です。
普段グラフを表示させるときは`plt.show()`などを用いますが、PyScriptでそれをすると、なにも表示されません。
パッケージごとに他にもローカルルールがあるかもしれません。

## 今のところ困ること
先日、東北大学のオープンキャンパス用にwebページを作っていて、PyScriptでアニメーションを表示させようとしたところ詰まりました。

```python
from matplotlib import animation
...
ani = animation.ArtistAnimation(fig, img, interval=50)
...
```
このコードを実行させようとしたところ、次のようなエラーが。
![スクリーンショット 2022-06-29 15.48.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611822/f8608cee-f3f4-731f-a4e4-a8d0f1823ba1.png)

```
PythonError: 
Traceback (most recent call last):
File "/lib/python3.10/site-packages/_pyodide/_base.py", line 429, in eval_code .run(globals, locals)
File "/lib/python3.10/site-packages/_pyodide/_base.py", line 300, in run coroutine = eval(self.code, globals, locals)
File "", line 6, in File "/lib/python3.10/site-packages/matplotlib/animation.py", line 1457, in __init__ super().__init__(fig, *args, **kwargs)
File "/lib/python3.10/site-packages/matplotlib/animation.py", line 1397, in __init__ event_source = fig.canvas.new_timer(interval=self._interval)
File "/lib/python3.10/site-packages/matplotlib/backends/browser_backend.py", line 468, in new_timer return TimerWasm(*args, **kwargs)
File "/lib/python3.10/site-packages/matplotlib/backend_bases.py", line 1097, in __init__ self.interval = 1000 if interval is None else interval
File "/lib/python3.10/site-packages/matplotlib/backend_bases.py", line 1139, in interval self._timer_set_interval()
File "/lib/python3.10/site-packages/matplotlib/backends/browser_backend.py", line 562, in _timer_set_interval if self._timer is not None: 
AttributeError: 'TimerWasm' object has no attribute '_timer'
```

単純に**TimerWasmには_timerなんてattributeはないよ！** というエラー。

この問題の根本にあるのは、ブラウザ上で非同期処理ができないということらしい（[How to fix matplotlib animation issue: 'TimerWasm' object has no attribute '_timer'](https://github.com/gzuidhof/starboard-notebook/issues/22)）。
まだアルファ版なので仕方ない部分なんですかね...

# 自作ライブラリも使いたい！
まだまだ不満な点は多いですが、PyScriptでは自作のライブラリを読み込んで使うこともできます。

例として次のような`hello.py`を準備してみましょう。

```hello.py
def greeting(name);
 print(f"Hello, I am {name}!")

def call():
 print("Who are you?")
```

これを読み込むためには、`<py-env>`タグを次のように書きます。
```index.html
...
<head>
  <link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css"/>
  <script defer src="https://pyscript.net/alpha/pyscript.js"></script>
  <py-env>
    - paths:
      - hello.py
  </py-env>
</head>
...
```
あとは外部ライブラリを読み込んだ時と同じことをすればOK。

```index.html
...
 <py-script>
from hello import *
name = "Qiiiiiiita"
greeting(name)
call()
 </py-script>
...
```

## しかし...
うまくいかない！`ModuleNotFoundError`が出てしまいます。
pathの通っているディレクトリに`hello.py`を置いても、簡易サーバーを立ち上げてもダメでした。

```
PythonError:
Traceback (most recent call last):
File "/lib/python3.10/site-packages/_pyodide/_base.py", line 429, in eval_code .run(globals, locals) 
File "/lib/python3.10/site-packages/_pyodide/_base.py", line 300, in run coroutine = eval(self.code, globals, locals) 
File "", line 4, in ModuleNotFoundError: No module named 'hello' 
```
他にもimportがうまくいかないという記事がありましたが、
一方でうまくいっている雰囲気の記事もあります（[HTML内にPythonコードを書いて実行できるPyScriptで遊んでみた！](https://zenn.dev/astrologian/articles/a76ce13d4426ec#pythonコードをモジュールとしてインポートしてhtml内で使用する)）。
方法は合っている気がするのだけど。

# 最後に。
PyScriptのポテンシャルを感じることはできましたが、
アルファ版ということもあって、本番環境では使えないですね！（そりゃそう）
更なる開発に期待しています。

でもコードなども公開されているので、ぜひご参考に。
[PyScript demo](https://pyscript.net/examples/)

それでは！