Streamlit vs PyWebIO Webフレームワーク対決！

# はじめに

## 概要

[「PyWebIO」があればPython 100%でWebアプリ作れるってマジ！？](https://qiita.com/payaneco/items/51a11a8063b7f0654561)で[PyWebIO](https://pywebio.readthedocs.io/en/latest/index.html)を紹介したらすごくLGTMしてもらった。(ありがとうございます！)
今回は[Streamlit](https://streamlit.io/)っていうWebフレームワークとPyWebIOを比較してみたよ。

前回記事の通り、PyWebIOを使えば**HTMLを一行も書かずにPython 100%で**Webアプリを作れるお手軽フレームワークだよ！

Streamlitは存在だけ知ってた。
でも勝手にグラフとかを表示するデザインフレームワークだと思ってたけど…調べてみたらそんなチャチなもんじゃ断じてなかった。
Streamlitを使えば**HTMLを一行も書かずにPython 100%で**Webアプリを作れるお手軽フレームワークを味わったよ！

## 結論

とりま(やや死語)煽ってみたけど、どっちも似たような機能のフロントエンドだと思う。
特性が違うから用途に応じてお好みのものを使えばいいんじゃない？

### PyWebIO

個人的な印象ではPyWebIOの方が標準オブジェクトで手厚くリッチな表現ができる。
余分な表示のない質実剛健なデザインを超速で作れるはず。
その一方で1画面にみっちり詰め込んだデザインとかを非同期処理で動かすのは苦労しそうなイメージがある。
**ウィザード形式**の入力画面に最適。

### Streamlit

Streamlitはデータ可視化機能が豊富に用意されてる。
[公式サイト]((https://streamlit.io/))で紹介されてる`streamlit hello`コマンドを打つだけでアニメーションとか地図とかグラフがぐねぐね動いて「おーさむ！[^1]」ってなること請け合い。
まだ2021年7月に出たばかりの[Version 0.84](https://docs.streamlit.io/en/stable/changelog.html#version-0-84-0)でセッション情報を扱う機能が追加されるなど、ページ遷移ロジックの定石は確立されてないようにも見える。
標準で用意されている機能が多く追加変更が早い分、凝り性の人はこだわりが多くなるかもしれない。

ちなみに[Sharing・Streamlit](https://streamlit.io/sharing)に申し込むことでHerokuやPython AnywhereのようにGithubから**爆速無料でWebアプリを公開できる**っぽい点も魅力的。

[^1]: "Awesome!"の意。決してこの記事が大いにサムいわけではない。

# インストールから実行まで

ようやく本題。
2つのフレームワークを使って似たようなWebアプリを作ってみた。
簡単なアプリばかりなので出来ばえに期待はしないでほしい。

## インストール

どっちも`pip install`でインストールできる。
ありがたい世の中だよね。

```
pip install streamlit
pip install pywebio
```

## Hello, Awesome Code!

下のサンプルコードはコードが乱れているが…すまない。暑すぎて大寒なネタしか思いつかなかったんだ。

### Streamlit

Streamlitのテキストはアンカーリンク付きで表示できる[title](https://docs.streamlit.io/en/stable/api.html#streamlit.title)とかコントロールが分かれて用意されてる。

さらに複数行コメントでシームレスにテキストを表示できる優れた機能もあるよ！
慣れてないとコメントアウトしたところが全文表示されたりするおちゃめ機能でもある。

```py
import streamlit as st

st.set_page_config(page_title="Streamlit")
st.title("Titleを炊いとる")
st.header("Headerも大変だ")
st.subheader("Subtitleでサブ鯛獲る")
st.text("モルカー\nあなた好かれてるのよ。")

"""
# 他にもコメントでマークダウンできる
[st.write](https://docs.streamlit.io/en/stable/api.html#streamlit.write)で様々な様式の出力もできる、らしい。
"""
```

### PyWebIO

タイトルとかを表示するコントロールが見つからなかった。
でも[put_markdown](https://pywebio.readthedocs.io/en/latest/output.html#pywebio.output.put_markdown)で表現できるよ！

```py
from pywebio.output import put_markdown, put_text
from pywebio.session import set_env

set_env(title="PyWebIO")
put_markdown("# Titleを炊いとる")
put_markdown("## Headerも大変だ")
put_markdown("### Subtitleでサブ鯛獲る")
put_text("モルカー\nあなた好かれてるのよ。")
```

## 対決！

Streamlitは最初から右上にハンバーガーが用意されててリロードとかできる。
画面読み込み中アイコンも右上に表示される。すごいね。

<img alt="StreamlitのHello1" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/81851/6d6c6f57-eb00-c132-d6de-be6e8f779c94.png" width=50%/><img alt="StreamlitのHello2" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/81851/7733470a-7778-b63a-91de-d87ba89162a8.png" width=50%/>

PyWebIOはフッターが表示されるくらいで至れり尽くせり機能はなさそうだがそれが良い。
アンカーリンクとかは表示されないけどidは振られてるし、出力されるHTMLのソースはStreamlitよりも比較的素直な気がする。

<img alt="PyWebIOのHello" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/81851/c9178d97-22be-fdf3-da31-39d96b0aa43b.png" width=50%/>

## コード実行

コード(`test.py`と仮定)を保存したフォルダでそれぞれコマンドを使うと実行できる。
どっちが好みかは人それぞれ。

### Streamlit

Djangoみたいに専用コマンドがある。

```
streamlit run .\test.py
```

### PyWebIO

BottleとかFlaskみたいにpython実行で呼び出す。

```
python .\test.py
```

# 入力コントロール

**※サンプルコードについて**
入力コントロールを　集めて早し　テーレッテレー(字余り)

## Streamlit

グループボックスとか、チェックボックスのラベル表示とかのやり方が分からなかったのでそれっぽく装飾してみた。

```py
import streamlit as st

st.title("Streamlit")
st.text_input("ねるねるねるねる", "は")
st.text("ねれば")
st.checkbox("ねるほど")
st.selectbox("色が", ("変わって",))
smile = st.slider("",  min_value=1, max_value=10, value=3)
if smile != -1:
    st.error("ひっ" * smile)
st.text_area("こうやって", value="つ\nけ\nて")
st.radio("うまい！", ("うまい！", "うまい！", "うまい！"))
```

## PyWebIO

普通にコーディングするとSubmitボタンが付いてくるよ。

```py
from pywebio.input import *
from pywebio.output import *

def show_smile(count):
    return "ひっ" * count

input_group("PyWebIO", 
    [input("ねるねるねるねる", name="neru", value="は"),
     checkbox("ねれば", name="more", options=["ねるほど"]),
     select("色が", name="color", options=["変わって"]),
     slider("", name="smile", value=3, max_value=10, validate=show_smile),
     textarea("こうやって", name="koneru", rows=3, value="つ\nけ\nて"),
     radio("うまい！", name="delicious", options=["うまい！", "うまい！", "うまい！"]),
    ])
```

## 対決！

Streamlitはデフォルトで凝ったデザインに見える。
PyWebIOは専有面積が狭いみたい。
笑い声はどっちもバリデーションで表現してる。

<img alt="StreamlitのInput" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/81851/129448e7-c389-b6be-f2ce-70b44a238a45.png" width=50%/><img alt="PyWebIOのInput" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/81851/468367a5-3345-d502-0252-c81e070164e9.png" width=50%/>

# 画面遷移

変数を保持しながら画面遷移するコードの比較だよ。
どっちのフレームワークもあんまり資料がなくて苦労したよ。

## Streamlit

グローバル変数とかは最初に読み込まれるだけで、ラジオボタンを選択し直しても再読み込みしてくれないっぽい。
対策として最近できた[Session State API](https://docs.streamlit.io/en/stable/session_state_api.html)を使ってセッションに残したデータを遷移先でも使うコードにしてみた。
※0.84より前でも一工夫すればできる

```py
import streamlit as st

#セッションの属性を読み書き
def show_page(voice):
    if "call_of_beauty" not in st.session_state:
        st.session_state.call_of_beauty = ""
    call = st.session_state.call_of_beauty
    call += voice
    st.session_state.call_of_beauty = call
    st.text(call)

#ページ遷移のようなもの
page = st.sidebar.radio("選択", ["ねこ", "いぬ", "てがみ"]) 
if page == "ねこ":
    show_page("にゃー")
elif page == "いぬ":
    show_page("わん")
elif page == "てがみ":
    show_page("！")
```


## PyWebIO

ラジオボタンの選択変更とかを待機する、いわゆるGUIのメインループ的な処理や非同期処理には[pywebio.pin](https://pywebio.readthedocs.io/en/latest/pin.html)のコントロールで[pin_wait_change](https://pywebio.readthedocs.io/en/latest/pin.html#pywebio.pin.pin_wait_change)する小技が必要らしい。
小技を使うとメインループで完結するからセッション使わずに変数を使いまわせるよ。
[参考資料](https://towardsdatascience.com/pywebio-1-3-0-add-tabs-pin-input-and-update-an-input-based-on-another-input-e81a139fefcb)

```py
from pywebio.pin import *
from pywebio.output import *

""" ちなみにセッションも簡単に使える
from pywebio.session import *
def show_page(voice):
    call = local.call_of_beauty or ""
    call += voice
    local.call_of_beauty = call
    return call
"""

calls = { "ねこ":"にゃー", "いぬ":"わん", "てがみ":"！" }
put_radio("my_selection", label="選択", options=calls.keys(), value="ねこ")

text = ""
while True:
    new_selection = pin_wait_change(["my_selection"])
    with use_scope("my_call", clear=True):
        key = new_selection["value"]
        if key in calls:
            text += calls[key]
        put_text(text)
```

## 対決！

Streamlitはレスポンシブなサイドバーが簡単に使えるうい奴なのだ。

PyWebIOはそういうガジェットはタブしか見つからなかった。
今回はStreamlitにあわせてラジオボタンで実装してみた。

<img alt="Streamlitの画面遷移" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/81851/7aefecb1-de42-f0a6-159e-efb2376540ee.png" width=50%/><img alt="PyWebIOの画面遷移" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/81851/846e235b-56cc-f528-a632-92b72b14897a.png" width=50%/>

# データ可視化

せっかくだから外部ツールと連携してData Virtualizationもしとく。
これをやっておくと**できる！SE**って感じがするからね！

matplotlibとかbokehとかnumpyのインストール方法は省略するよ。

## Streamlit

標準機能でグラフ表示くらいできるし連携できるツールも幅広いみたい。
今回はすごく普通にmatplotlibと連携してサイン波を表示するサンプルにしたよ。

```py
import matplotlib.pyplot as plt
import numpy as np
import streamlit as st

st.title("Streamlit")
st.text("サインいっこいれる")
fig = plt.figure()
xs = np.linspace(0, np.pi*4, 500)
plt.plot(xs, np.sin(xs))
st.pyplot(fig)
```

## PyWebIO

[公式サイト](https://pywebio.readthedocs.io/en/latest/libraries_support.html#data-visualization)によると、Bokehとかpyechartsとかplotlyと連携できるらしい。
種類は多くないけど必要十分なツールと連携できるイメージ。
公式サイトのデモを見たところ、大抵のツールは出力したhtmlを[put_html](https://pywebio.readthedocs.io/en/latest/output.html#pywebio.output.put_html)で読み取る仕組みなのかな？

今回はnotebook_typeが用意されてるBokehでサンプルを作ったよ。

```py
import pywebio
from pywebio.output import put_text
from bokeh.io import output_notebook
from bokeh.plotting import figure, show
import numpy as np

put_markdown("# PyWebIO")
put_text("サインいっこいれる")
xs = np.arange(0, np.pi*4, 0.1)
p = figure()
p.line(xs, np.sin(xs))
output_notebook(notebook_type='pywebio')
show(p)
```

## 対決！

どっちのフレームワークもコード短いし、忠実に連携ツールを表示しててえらい！

<img alt="2021-08-07 (11).png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/81851/daa9154d-1aad-53b1-86d8-308711db49ba.png" width=50%/><img alt="2021-08-07 (12).png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/81851/75e3c01b-abd4-e7ad-d8fe-551e752a1138.png" width=50%/>


# 講評

この記事では本当に`<br>`すら書かずにWebアプリのサンプルを作っちゃった。
フレームワークのコードに癖がないとは言わないけど、そんなにフレームワーク依存のおまじないが多くないし可読性も低くないよね。

ということで、ラズパイでIoTしたデータをWebで表示したり操作したい人も、ビッグデータ分析させられて「Kibana使わずに部分的なデータだけ公開しろ」とか無茶ぶりされた人も、これらのフロントエンドを使えばハッピーエンドかもしれない。

これらのフレームワークのもたらすものはDI注入どころではなく、**Py注入**と言っても過言ではないのではないか！
と、今考えた造語を投げたところで筆をおかせていただきます。ありがとうございました。