Streamlitの新機能マルチページを触ってみる



ロックの日初投稿です。

PythonコードだけでWebアプリが作れる[Streamlit](https://streamlit.io/)ですが
先日Ver.1.10.0がリリースされてマルチページ機能が実装されました。
少し触ってみたのでまとめていきます。

https://blog.streamlit.io/introducing-multipage-apps/

# 環境
```
Python 3.10.4
Streamlit 1.10.0
```

# マルチページ機能とは
そもそもマルチページとはなんぞやという話ですが、
字面の通り、複数のページを用意できる機能です。

Streamlitは通常1ページの構成で、そのページの上ですべての動作が完結します。
ただ機能ごとにページを分けたいのが人情というものです。
そこで下記サイトのように、st.selectboxやst.radioをサイドバーに配置してマルチページを実装するのが一般的でした。

https://share.streamlit.io/andfanilo/streamlit-echarts-demo/master/app.py

ユーザがあれこれ工夫を凝らしている中、満を持して標準機能としてマルチページが実装されたのです。

# 使い方
## 基本的な使い方
streamlit runコマンドで実行するPythonスクリプトと同じ階層に`pages`という名前のフォルダを作成します。
その`pages`フォルダ内にマルチページとして設定したいPythonスクリプトを保存します。

ここでは、streamlit runで実行するスクリプトとしてstreamlit_app.pyと`pages`フォルダ内にpage_a.pyとpage_b.pyを用意します。

```:ファイル構成
streamline_app.py
pages
 L page_a.py
 L page_b.py
```

それぞれの中身は適当に･･･

```python:streamlit_app.py
import streamlit as st

st.write("Hello Streamlit World!")
```

```python:pages/page_a.py
import streamlit as st

st.write("Page A")
```

```python:pages/page_b.py
import streamlit as st

st.write("Page B")
```

<br>

あとは今までのStreamlitと同様に`streamlit run`コマンドを実行します。

```bash
streamlit run streamlit_app.py
```

実行スクリプトと`pages`フォルダ内のスクリプトがサイドバーに表示され、
それぞれを選択することで、各ページが表示されます。
![streamlit_multipage_1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/414198/2a506a2b-15eb-f941-70ed-48c7f622e37a.gif)

## マルチページの挙動について
ページ一覧がサイドバーに表示されますが、サイドバーに他のStreamlitパーツを表示したときなど
マルチページを使用するときのStreamlitの挙動を確認していきます。

### サイドバーにStreamlitパーツを表示する
サイドバーにStreamlitパーツを表示させると、ページ選択の下部に表示されます。

![スクリーンショット 2022-06-08 22.25.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/414198/981f118f-1849-3e35-c36e-a14c3238c491.png)


### ページごとにst.set_pade_configを設定できる
Webページとしての設定（ページタイトルやアイコンなど）を指定する`st.set_pade_config`は、通常はアプリ全体で1つしか設定できませんが、
マルチページの場合は各ページごとに設定ができます。

下記の実行例では、各ページごとに異なるページタイトルと、streamlit_app･page bはワイドレイアウトを設定しています。

![streamlit_multipage_2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/414198/a74045b4-aa9f-2a94-6b49-04e778b371cf.gif)


### ページの順番を操作する
`pages`フォルダ内のスクリプトに関しては、サイドバーに表示される順番も変更できます。
各スクリプトに`01_`や`02_`のように表示したい順番に接頭語をつけると、その通りの順番で表示されます。
先ほどまではpage_a・page_bの順番でしたが、接頭語を追加して順番を入れ替えてみます。

```:ファイル構成
streamline_app.py
pages
 L 02_page_a.py
 L 01_page_b.py
```

接頭語はサイドバー上では省略されて表示されます。
![スクリーンショット 2022-06-08 22.14.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/414198/69cb49ed-1451-a500-7f76-4aa2cdfb7603.png)



### pagesの中にpagesを設置することはできない
pagesの中にさらにpagesを置いてみましたが、サイドバーには表示されませんでした。
```
streamline_app.py
pages
 L page_a.py
 L page_b.py
 L pages
   L page_c.py
   L page_d.py
```
いまのバージョンではマルチページのネストを深めるためには、下記のような従来手法を組み合わせる必要があります。

# 従来のマルチページとの違い
「従来」という言い方が正しいのかはわかりませんが、
[マルチページとは](#マルチページとは)で書いたように、今までStreamlitでマルチページを実装するときには、
st.radioやst.selectboxのようなStreamlitパーツとif文を組み合わせて実装していました。
```python:従来のマルチページ
import streamlit as st


def page_main():
    st.write("Main Page")


def page_a():
    st.write("Page A")


def page_b():
    st.write("Page B")


selected_page = st.sidebar.radio("メニュー", ["Main Page", "Page A", "Page B"])

if selected_page == "Main Page":
    page_main()
elif selected_page == "Page A":
    page_a()
elif selected_page == "Page B":
    page_b()
else:
    pass
```

![streamlit_multipage_3.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/414198/6754bb8f-6c5b-a421-4915-6bb4f531c5af.gif)



## メリット・デメリット
従来の手法と新たに追加されたマルチページ機能それぞれメリット・デメリットがあります。
### マルチページ機能
- メリット
    - 使い方・ファイル構成がシンプル
    - UIがスッキリする
    - 各ページごとに異なるst.set_pade_configを設定できる
-  デメリット
    -  ファイル構成が固定化（pagesフォルダと1ページごとに1スクリプトが必要）される
    -  関数で呼び出すわけでないのでパラメータのやり取りに工夫が必要

一番のメリットは手軽さです。
`pages`フォルダにスクリプトを入れておけばいいので、分岐や関数を使わない分、
初学者のハードルが下がりますし、ソースコードを読む際もどのコードにどのページの処理が書かれているのか追いやすくなります。

従来の手法ではUIがラジオボタンやプルダウンになるため野暮っぽさが出てしまいますが、多少あか抜けた印象にもなっています。

st.set_pade_configを個別に設定できるのも、私はいままで必要を感じたことがないので何とも言えませんが、
柔軟性があって悪いことはないのかなと思います。

デメリットは手軽さと表裏一体で、
従来の手法では機能ごとにスクリプトやフォルダを自由にわけることができましたが、各ページを`pages`フォルダに入れておかないといけないので
ファイル構成の自由度は下がります。（好きにファイルを作って、各ページから呼びだせばそれで済む話ですが）

また上の従来の手法のサンプルコードのように関数で各ページを呼び出すことができるので、引数の受け渡しも容易でした。
Streamlitは`st.session_state`変数というStreamlitアプリ全体で共有できる変数がありますが、
通常の変数と値の保持仕方が異なる・スコープが広いなど個性の強い機能なので
きちんと管理しないと思わぬ動作の原因になります。
パラメータの受け渡しに関しては工夫が必要になります。

### 従来手法
- メリット
    - 各ページを関数として実装できるため、アルゴリズム・ファイル構成の柔軟性が高い
    - ネストの深いマルチページの設定ができる
- デメリット
    - ページ切り替えのUIがラジオボタンやプルダウンのため、野暮ったくみえる
    - st.set_pade_configは全ページで共通
 
従来手法のメリット・デメリットはマルチページ機能のそれと対になっています。

ネストの深いマルチページとは、
例えばページAを表示したときはページ1,2,3にアクセスするためのメニューが表示される、
ページBを表示したときはページ4,5,6にアクセスするためのメニューが表示されるなど
アクセスできるマルチページを柔軟に変更できるということです。
（先ほど例で出した[Streamlitアプリ](https://share.streamlit.io/andfanilo/streamlit-echarts-demo/master/app.py)も、選択した項目によって表示できるページが変わっています）

![streamlit_multipage_4.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/414198/965419dd-b7a3-4df3-92f2-ae491d32104c.gif)

マルチページ機能ではすべてのページが選べるように表示されるため、このような柔軟な運用ができません。

# さいごに
新しく実装されたマルチページ機能で容易にマルチページを用意することができるようになりました。
使い方がシンプルなのでコードもシンプルになりますし、1ページ1スクリプトという対応関係になっているのでコードの可読性も高くなります。
ただ手軽な分、柔軟性の面は劣るので必要に応じて今までマルチページ機能を実装していた方法を組み合わせる必要がありそうです。

# 参考
- [Introducing multipage apps!](https://blog.streamlit.io/introducing-multipage-apps/)