streamlitで別ページ（タブ）にデータを持ち越す

# 1.最初に
データ分析結果をすばやくwebappとして可視化できる[streamlit](https://www.streamlit.io/)。
色々公式サイトにサンプルがあるので楽しい。最近見たのはhuggingfaceが[公開済のDatasetを見る](https://huggingface.co/nlp/viewer/?dataset=aeslc)のに使っていたもの。

streamlitをとても便利に使わせてもらっているが、下のページのようにラジオボタンなどで別のページ（タブ）に移動すると、変数やdataframeがすべて消えてしまう。
「入力データ」ページでデータをアップロードして「データ加工」ページへ行くとアップロードしてdataframeに入れたデータが参照できない。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/385220/54badd0a-5565-d6aa-0e5f-5608d6271a96.png)


データアップロードページ→データ加工ページ→データ可視化ページなど。
複数ページにまたがってデータを処理したい場合に困った。
１つのページ内で完結させようとすると、今のstreamlitの仕様上すごく縦に長くなってしまう。

# 2.まとめ
色々ネット上をさまよって、最後にsessionを使って中継するという方法が良かったので書いておく。
肝の部分↓

<details><summary>sessionの部分</summary><div>

```python
class _SessionState:

    def __init__(self, session, hash_funcs):
        """Initialize SessionState instance."""
        self.__dict__["_state"] = {
            "data": {},
            "hash": None,
            "hasher": _CodeHasher(hash_funcs),
            "is_rerun": False,
            "session": session,
        }

    def __call__(self, **kwargs):
        """Initialize state data once."""
        for item, value in kwargs.items():
            if item not in self._state["data"]:
                self._state["data"][item] = value

    def __getitem__(self, item):
        """Return a saved state value, None if item is undefined."""
        return self._state["data"].get(item, None)
        
    def __getattr__(self, item):
        """Return a saved state value, None if item is undefined."""
        return self._state["data"].get(item, None)

    def __setitem__(self, item, value):
        """Set state value."""
        self._state["data"][item] = value

    def __setattr__(self, item, value):
        """Set state value."""
        self._state["data"][item] = value
    
    def clear(self):
        """Clear session state and request a rerun."""
        self._state["data"].clear()
        self._state["session"].request_rerun()
    
    def sync(self):
        """Rerun the app with all state values up to date from the beginning to fix rollbacks."""

        # Ensure to rerun only once to avoid infinite loops
        # caused by a constantly changing state value at each run.
        #
        # Example: state.value += 1
        if self._state["is_rerun"]:
            self._state["is_rerun"] = False
        
        elif self._state["hash"] is not None:
            if self._state["hash"] != self._state["hasher"].to_bytes(self._state["data"], None):
                self._state["is_rerun"] = True
                self._state["session"].request_rerun()

        self._state["hash"] = self._state["hasher"].to_bytes(self._state["data"], None)


def _get_session():
    session_id = get_report_ctx().session_id
    session_info = Server.get_current()._get_session_info(session_id)

    if session_info is None:
        raise RuntimeError("Couldn't get your Streamlit Session object.")
    
    return session_info.session


def _get_state(hash_funcs=None):
    session = _get_session()

    if not hasattr(session, "_custom_session_state"):
        session._custom_session_state = _SessionState(session, hash_funcs)

    return session._custom_session_state


```

</div></summary>


# 3．具体的例
上記のをコードに入れておけば、

```python
def main():
    state = _get_state()
    pages = {
        "入力データ":in_page,
        "データ加工":tran_page,
        "データ可視化":out_page
    }

    #st.sidebar.title(":floppy_disk: Page states")
    page = st.sidebar.radio("ページ選択", tuple(pages.keys()))

    # Display the selected page with the session state
    pages[page](state)

    # Mandatory to avoid rollbacks with widgets, must be called at the end of your app
    state.sync()

```
でメインページを作っておいて、
受け渡したいページと受け入れたいページとをstate.~~でつなげばいい。
当然ながらdataframeも受け渡しできた。

```python
def in_page(state):
    ~~~
    
    #受け渡したい変数をstate.~~で入れて、
    state.dataframe1 = dataframe1
    state.var1 = var1

def tran_page(state):
    """
　　　#受け入れたい変数を ~~=state.~~ で受付る。
    dataframe1 = state.dataframe1
    var1 = state.var1
    ~~~
    またはそのままstate.dataframe1
    state.var1
　　で使う。
    """

def out_page(state):
    ~~~

#最後にmain()を入れて実行する。
main()

```


20201203追加。
st.experimental_set_query_params 
st.experimental_get_query_params
で同等の事ができそう！
↓参照
https://link.medium.com/sz1bpLvyUbb

# 4.参照
* https://discuss.streamlit.io/t/multi-page-app-with-session-state/3074/24
* https://huggingface.co/nlp/viewer/?dataset=aeslc