0 引入
我們?cè)?a href="http://www.reibang.com/p/abd05b381a23" target="_blank">streamlit搭建ML前端demo這篇文章里簡(jiǎn)單介紹了streamlit這個(gè)搭建前端頁(yè)面的神器堂竟。我們可以通過python腳本編寫實(shí)現(xiàn)一個(gè)簡(jiǎn)單的展示頁(yè)面忆肾。那如果你同時(shí)有N個(gè)頁(yè)面想要去展示泡态,應(yīng)該怎么樣去做呢蕊连?今天就探討一下怎樣利用streamlit實(shí)現(xiàn)多頁(yè)面展示访忿。
1 腦洞大開
我在這里列了幾種可以實(shí)現(xiàn)多頁(yè)面展示的方法菩彬,供大家參考潮梯。
- 通過server.port參數(shù)指定運(yùn)行端口骗灶,同時(shí)開多個(gè)頁(yè)面。
- 多個(gè)頁(yè)面合并在一個(gè)頁(yè)面內(nèi)秉馏,通過某個(gè)組件實(shí)現(xiàn)頁(yè)面跳轉(zhuǎn)耙旦。
- 看看有沒有開源工具包可以用,避免重復(fù)造輪子萝究。
下面就來講下這幾種實(shí)現(xiàn)方法免都。
2 多頁(yè)面實(shí)現(xiàn)
2.1 同時(shí)開多個(gè)頁(yè)面
我們之前在streamlit搭建ML前端demo里介紹過,streamlit運(yùn)行的時(shí)候是通過命令行執(zhí)行服務(wù)啟動(dòng)命令的帆竹。運(yùn)行命令如下
streamlit run your_script.py [-- script args]
注意:這里傳入自己需要用到的參數(shù)時(shí)绕娘,必須使用雙橫線,否則參數(shù)就被視為streamlit的參數(shù)栽连,而非你腳本的參數(shù)险领。
也可以傳入配置選項(xiàng)來修改應(yīng)用端口绢陌、禁用自動(dòng)保存等特性。要查看可用選型熔恢,運(yùn)行 如下命令:
streamlit run --help
執(zhí)行后的結(jié)果如下圖所示:
這里的--global.disableWatchdogWarning就是streamlit的一個(gè)配置選項(xiàng)脐湾,類型是BOOLEAN,下邊是對(duì)這個(gè)參數(shù)的描述叙淌。
那我們可以找到--server.port這個(gè)參數(shù)秤掌,它是INTEGER類型的愁铺。運(yùn)行不同app.py腳本的時(shí)候,指定一下這個(gè)參數(shù)即可椒拗。
streamlit run app.py --server.port 5002
就可以看到這個(gè)頁(yè)面是在5002端口啟動(dòng)的。
2.2 多頁(yè)面跳轉(zhuǎn)
我主要參考了這個(gè)github提到的多頁(yè)面配置辦法获黔。通過新建一個(gè)MultiApp類蚀苛,做多個(gè)頁(yè)面的添加和運(yùn)行,這樣只需要在一個(gè)端口運(yùn)行服務(wù)玷氏。
那如何在不同頁(yè)面之間跳轉(zhuǎn)呢堵未?那就需要用到streamlit內(nèi)置的一些前端組件了,如st.sidebar.selectbox(顯示列表選擇框)或者st.sidebar.radio(顯示單選框)盏触。就可以實(shí)現(xiàn)多個(gè)頁(yè)面之間跳轉(zhuǎn)了渗蟹。
來,上代碼赞辩,咱們學(xué)習(xí)下人家的idea雌芽。
import streamlit as st
class MultiApp:
"""Framework for combining multiple streamlit applications.
Usage:
def foo():
st.title("Hello Foo")
def bar():
st.title("Hello Bar")
app = MultiApp()
app.add_app("Foo", foo)
app.add_app("Bar", bar)
app.run()
It is also possible keep each application in a separate file.
import foo
import bar
app = MultiApp()
app.add_app("Foo", foo.app)
app.add_app("Bar", bar.app)
app.run()
"""
def __init__(self):
self.apps = []
def add_app(self, title, func):
"""Adds a new application.
Parameters
----------
func:
the python function to render this app.
title:
title of the app. Appears in the dropdown in the sidebar.
"""
self.apps.append({
"title": title,
"function": func
})
def run(self):
app = st.sidebar.radio(
'Go To',
self.apps,
format_func=lambda app: app['title'])
app['function']()
其實(shí)很簡(jiǎn)單,去除注釋辨嗽,代碼沒幾行世落。主要實(shí)現(xiàn)的功能是,通過一個(gè)列表apps存儲(chǔ)不同的頁(yè)面糟需,利用st.sidebar.radio選擇title對(duì)應(yīng)的頁(yè)面function并執(zhí)行屉佳。每個(gè)頁(yè)面有一個(gè)title和一個(gè)function組成,這里的function是每個(gè)頁(yè)面上需要展示的內(nèi)容洲押。
那我們自己運(yùn)行一下他給的例子吧武花,代碼如下。
def foo():
st.title("Hello Foo")
def bar():
st.title("Hello Bar")
app = MultiApp()
app.add_app("Foo", foo)
app.add_app("Bar", bar)
app.run()
執(zhí)行下杈帐,默認(rèn)是選擇了第一個(gè)頁(yè)面Foo体箕,咱們點(diǎn)擊第二個(gè)Bar,也能跳轉(zhuǎn)到第二個(gè)頁(yè)面上去挑童。
再來點(diǎn)擊下第一個(gè)Foo干旁,結(jié)果發(fā)現(xiàn),哎炮沐,頁(yè)面展示的不對(duì)勁争群。怎么內(nèi)容還停留在第二個(gè)頁(yè)面呢。
再去命令行里邊看看大年,里邊也有報(bào)錯(cuò)换薄。報(bào)錯(cuò)內(nèi)容如下:
去github看看有沒有issue玉雾,還真找到一個(gè)同樣的問題∏嵋看里邊作者提供的解決方案是复旬,重寫了run()函數(shù),重寫后的代碼內(nèi)容如下冲泥。
def run(self):
app_state = st.experimental_get_query_params()
app_state = {k: v[0] if isinstance(v, list) else v for k, v in app_state.items()} # fetch the first item in each query string as we don't have multiple values for each query string key in this example
#st.write('before', app_state)
titles = [a['title'] for a in self.apps]
functions = [a['function'] for a in self.apps]
default_radio = titles.index(app_state["page"]) if "page" in app_state else 0
title = st.sidebar.radio(
'Go To',
titles,
index=default_radio,
key='radio')
app_state['page'] = st.session_state.radio
#st.write('after', app_state)
st.experimental_set_query_params(**app_state)
functions[titles.index(title)]()
看起來就麻煩一些了驹碍,需要對(duì)每次的app狀態(tài)參數(shù)page做一個(gè)獲取,然后跳轉(zhuǎn)到一個(gè)頁(yè)面后及時(shí)更新這個(gè)page參數(shù)凡恍。
那還有沒有更簡(jiǎn)單的操作呢志秃?筆者看報(bào)錯(cuò)信息發(fā)現(xiàn),在點(diǎn)擊Foo的時(shí)候嚼酝,報(bào)的是Bar這個(gè)dict不在列表里浮还,難道是列表在點(diǎn)擊的時(shí)候有啥變化?
那咱們?cè)?code>app['function']()這行之前加個(gè)print
函數(shù)闽巩,打印一下self.apps看看吧钧舌。結(jié)果真的發(fā)現(xiàn)了問題,如下圖所示涎跨。每次點(diǎn)擊一個(gè)頁(yè)面洼冻,self.apps里邊的function函數(shù)在內(nèi)存里邊就變了,這顯然是個(gè)小bug隅很。
那么碘赖,我們就不要這么存了就行了呀,列表里只存title外构,function根據(jù)點(diǎn)擊的title去獲取普泡,問題迎刃而解。自己嘗試了一種新的代碼寫法审编。
import streamlit as st
class MultiApp:
def __init__(self):
self.apps = []
self.app_dict = {}
def add_app(self, title, func):
if title not in self.apps:
self.apps.append(title)
self.app_dict[title] = func
def run(self):
title = st.sidebar.radio(
'Go To',
self.apps,
format_func=lambda title: str(title))
self.app_dict[title]()
再去執(zhí)行一下看看撼班,結(jié)果正常,不會(huì)再報(bào)錯(cuò)了垒酬。
2.3 開源工具包
在谷歌上搜了下砰嘁,還真有開源的工具包,streamlit-multipage勘究。項(xiàng)目地址在這里矮湘,支持pip安裝。
項(xiàng)目支持的功能比較多口糕,除了單頁(yè)面缅阳、多頁(yè)面之外,還可以自定義命名空間景描、自定義UI等等十办。缺點(diǎn)就是秀撇,你得花時(shí)間熟悉別人的代碼思路,改起來會(huì)有點(diǎn)費(fèi)勁向族。其中多頁(yè)面部分代碼如下呵燕。
import streamlit as st
from streamlit_multipage import MultiPage
def input_page(st, **state):
st.title("Body Mass Index")
weight_ = state["weight"] if "weight" in state else 0.0
weight = st.number_input("Your weight (Kg): ", value=weight_)
height_ = state["height"] if "height" in state else 0.0
height = st.number_input("Your height (m): ", value=height_)
if height and weight:
MultiPage.save({"weight": weight, "height": height})
def compute_page(st, **state):
st.title("Body Mass Index")
if "weight" not in state or "height" not in state:
st.warning("Enter your data before computing. Go to the Input Page")
return
weight = state["weight"]
height = state["height"]
st.metric("BMI", round(weight / height ** 2, 2))
app = MultiPage()
app.st = st
app.add_app("Input Page", input_page)
app.add_app("BMI Result", compute_page)
app.run()
3 總結(jié)
本文主要介紹了如何利用streamlit實(shí)現(xiàn)多個(gè)前端頁(yè)面的幾種方法。由易到難的介紹了改端口件相、封裝類再扭、引入開源包的思路。希望對(duì)大家有所幫助夜矗。