Streamlit簡介
Streamlit是一個基於Python的可視化工具,和其他可視化工具不同的是,它生成的是一個可交互的站點(頁面)。但同時它又不是我們常接觸的類似Django、Flask這樣的WEB框架。當前使用下來的感受:
缺點:
- 自帶服務器,且需要從命令行啟動服務方能查看頁面,不能通過直接運行Python代碼的方式啟動應用。即不能集成到其他系統及框架中
- 當前只支持單頁面,不能通過URL傳參等方式生成多頁面。Github有人提了Issues,但是目前還沒支持,部分人給了一些臨時的Hack
- 目前不支持登錄驗證,且由於自帶服務器的原因,很難集成其他第三方的登錄驗證等,不能做很好的數據權限控制
優點:
- 無需編寫任何HTML、CSS或JS代碼就可以生成界面不錯的頁面
整體評價:優勢明顯,定位於只熟悉Python代碼的算法人員。雖然目前的功能比較簡陋,問題較多,但隨着不斷的開發,相信功能也會也來越強大。
Streamlit帶來的改變
原先的數據展示頁面開發流程:
- 在Jupyter中開發演示
- 將Python代碼復制到文件
- 編寫Flask應用,包括考慮HTTP請求、HTML代碼、JS和回調等

而當展示頁面非常重要時,通常的流程是這樣的:
- 收集用戶需求
- 定義展示框架與原型
- 使用HTML、CSS、Python、React、Javascript等進行編碼
- 一個月以后才能看到最終的頁面

Streamlit的流程:

而能夠快速生成應用,主要原因是Streamlit兼容以下Python庫或框架:
- 數據處理:Numpy、Pandas
- 機器學習框架或庫:Scilit-Learn、TensorFlow、Keras、PyTorch
- 數據可視化工具:matplotlib、seaborn、poltly、boken、Altair、GL、Vega-Lite
- 文本處理:Markdown、LaTeX
Streamlit使用指南
上面介紹的感覺有些摸不着頭腦,接下來直接用代碼來演示。
常用命令
streamlit run your_script.py
顯示文本
st.subheader("Data Analytics")
st.markdown('Streamlit is **_really_ cool**.')
super(MyClass, self).__init__()
"email": "admin@test.com",
展現內容如下:

從上面可以看到,Streamlit可以非常方便的相似不同層級的title(目前只支持3種,類似H1~H3),同時支持文本、LaTeX、Markdown、Code和JSON、Emoji的輸出。也支持信息的反饋的支持(成功、警告、錯誤)。
另外st.write()支持各種類型的數據:
- write(string):顯示字符串或Markdown 字符串,同時支持LaTeX、emoji shortcodes
- write(data_frame) : 顯示DataFrame表格
- write(error) : 顯示錯誤信息
- write(func) : 顯示function信息
- write(module) : 顯示module的信息
- write(dict) : Displays dict in an interactive widget.
- write(obj) : 打印字符串對象
- write(mpl_fig) : 顯示Matplotlib圖片
- write(altair) : 顯示Altair圖表
- write(keras) : 顯示Keras模型
- write(graphviz) : 顯示Graphviz圖片
- write(plotly_fig) : 顯示Plotly圖片
- write(bokeh_fig) : 顯示Bokeh圖片
- write(sympy_expr) : 使用LaTeX 顯示SymPy語法
顯示數據
df = pd.DataFrame(np.random.randn(10, 20), columns=('col %d' % i for i in range(20)))
顯示內容:


可以看到,使用st.dataframe(df)相比st.table(df)界面要好看些。
顯示圖表
import matplotlib.pyplot as plt
df = pd.DataFrame(np.random.randn(20, 3), columns=['a', 'b', 'c'])
arr = np.random.normal(1, 1, size=100)
df_location = pd.DataFrame(np.random.randn(1000, 2) / [50, 50] + [37.76, -122.4], columns=['lat', 'lon'])



可以看到這里支持目前市面上的各種圖表的Python庫。
顯示媒體
這里的媒體包括:圖片、音頻、視頻。
這里不詳細介紹,具體可以見官方文檔
交互組件
if st.button('Say hello'):
st.write('Why hello there')
agree = st.checkbox('I agree')
genre = st.radio("What's your favorite movie genre", ('Comedy', 'Drama', 'Documentary'))
st.write('You selected comedy.')
st.write("You didn't select comedy.")
option = st.selectbox('How would you like to be contacted?', ('Email', 'Home phone', 'Mobile phone'))
st.write('You selected:', option)
options = st.multiselect('What are your favorite colors', ['Green', 'Yellow', 'Red', 'Blue'], ['Yellow', 'Red'])
st.write('You selected:', options)
age = st.slider('How old are you?', 0, 130, 25)
st.write("I'm ", age, 'years old')
values = st.slider('Select a range of values', 0.0, 100.0, (25.0, 75.0))
st.write('Values:', values)
title = st.text_input('Movie title', 'Life of Brian')
st.write('The current movie title is', title)
number = st.number_input('Insert a number')
st.write('The current number is ', number)
txt = st.text_area('Text to analyze', '''
It was the best of times, it was the worst of times, it was
the age of wisdom, it was the age of foolishness, it was
the epoch of belief, it was the epoch of incredulity, it
was the season of Light, it was the season of Darkness, it
was the spring of hope, it was the winter of despair, (...)
uploaded_file = st.file_uploader("Choose a CSV file", type="csv")
if uploaded_file is not None:
data = pd.read_csv(uploaded_file)
color = st.beta_color_picker('Pick A Color', '#00f900')
st.write('The current color is', color)

側邊欄
option = st.sidebar.write("Hello World!")
緩存機制
Streamlit遵循由上至下的運行順序,所以每次代碼中有進行任何更改,都會重新開始運行一遍,會十分耗時。@st.cache會對封裝起來的函數進行緩存,避免二次加載。如果函數中的代碼發生變動,cache會重新加載一遍並緩存起來。假如將代碼還原到上一次版本,由於先前的數據已經緩存起來了,所以不會進行二次加載。
@st.cache # This function will be cached
def my_slow_function(arg1, arg2):
# Do something really slow in here!
當使用@st.cache 裝飾器標記一個函數時,這將告訴steamlit在此函數被調用的時候應當檢查以下事情:
- 緩存函數的輸入參數
- 緩存函數使用的外部變量的值
- 緩存函數主體
- 緩存函數中調用到的函數的主題
若streamlit是初次看到這四個部分的確切值,組合方法和順序,那么streamlit將執行這個函數並將結果保存在本地緩存中。下次當緩存函數被調用的時候,若這些部分沒有變化,streamlit將直接返回之前緩存中的結果作為輸出。

舉個例子,當函數 expensive_computation(a, b), 被 @st.cache裝飾時,並以 a=2 和 b=21執行, Streamlit會進行以下操作:
- 計算緩存的key
- 若key可以在緩存中找到,則:
- 提取出以前的緩存元組(緩存輸出和緩存輸出的hash)
- 執行輸出突變檢查, 計算輸出的新哈希並將其與存儲的output_hash進行比較。
- 若兩hash不同,顯示告警Cached Object Mutated。(Note: 設置allow_output_mutation=True 可以禁用這步)。
- 若輸入的hash在緩存中找不到:
- 執行緩存函數 (i.e. output = expensive_computation(2, 21))
- 根據函數的output計算output_hash
- 將key → (output, output_hash)存入緩存
- 返回輸出
如果遇到錯誤,則會引發異常。.如果在對鍵或輸出進行哈希處理時發生錯誤,則會引發UnhashableTypeError 錯誤。
如上所述,Streamlit的緩存功能依賴於散列來計算緩存對象的鍵,並檢測緩存結果中的意外變化。為了增強表達能力,Streamlit允許您使用hash_funcs參數覆蓋此哈希過程。比如函數打開一個文件,默認情況下,它的hash是屬性filename的hash。只要文件名不變,哈希值將保持不變。可以使用@st.cache裝飾器的 hash_funcs 參數:
return (input_io.getvalue(), input_io.tell())
@st.cache(hash_funcs={io.BytesIO: hash_io, io.StringIO: hash_io})
def load_data(file_data):
return pd.read_csv(file_data)
return pd.read_excel(file_data)
Streamlit使用Hack
掩藏底部鏈接
hide_st_style = "<style># MainMenu {visibility: hidden;} footer {visibility: hidden;}</style>"
st.markdown(hide_st_style, unsafe_allow_html=True)
支持使用folium展示地圖
st.markdown(map._repr_html_(), unsafe_allow_html=True)
如下報錯:(地圖不能顯示)
Make this Notebook Trusted to load map: File -> Trust Notebook
解決方案:pip install branca==3.1.0
支持通過URL獲取參數
備注:僅在0.58.0版本測試通過,新版本應該已經支持獲取URL參數了。
1、找到tornado的安裝路徑
2、修改tornado下的routing.py文件
class _RoutingDelegate(httputil.HTTPMessageDelegate):
def __init__(self, router, server_conn, request_conn):
self.server_conn = server_conn
self.request_conn = request_conn
self.router = router # type: Router
def headers_received(self, start_line, headers):
request = httputil.HTTPServerRequest(
connection=self.request_conn,
server_connection=self.server_conn,
start_line=start_line, headers=headers)
self.request_conn.params.urlpara = dict()
psl = request.query.split('&')
self.request_conn.params.urlpara[pl[0]]=pl[1]
self.delegate = self.router.find_handler(request)
if self.delegate is None:
app_log.debug("Delegate for %s %s request not found",
start_line.method, start_line.path)
self.delegate = _DefaultMessageDelegate(self.request_conn)
return self.delegate.headers_received(start_line, headers)
3、重啟應用后進行測試:https://127.0.0.1:8501/?mypara1=99
from streamlit.server.Server import Server
sessions = Server.get_current()._session_info_by_id
session_id_key = list(sessions.keys())[0]
session = sessions[session_id_key]
urlPara = session.ws.request.connection.params.urlpara
st.write("URL PARAM:"+str(urlPara))支持
DataFrame支持樣式定義問題
df.style.format({'col1': '{:.0f}', 'col2': '{:,.2%}'})
通過以上方法可以控制dataframe中字段的顯示精度問題或按百分比顯示等,但往往會報錯,原因是pandas 1.1.0升級后導致的。
解決方案是退回老版本:pip install pandas==1.0.0
支持不同URL類型的不同頁面共存
上面URL獲取參數存在一定的Bug,即當訪問一個帶參數的URL過后,再刷新其他的頁面,以前帶的參數會還保存下來,解決方式是部署不同頁面,不同頁面使用不同的端口。然后再通過Nginx進行URL映射:
server_name streamlit.domain.com;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 86400;
proxy_pass http://172.17.0.2:8501/;
proxy_pass http://172.17.0.2:8502/;
proxy_pass http://172.17.0.2:8503/;
支持賬號密碼登錄
Stremlit本身不帶登錄驗證,對於一些數據型的應用可能需要權限才能查看,一種簡單的方法是每個應用最外層加上輸入框,在用戶輸入的字符不等於代碼中支付時,不顯示內容。另外一種方案是,可以使用nginx的auth:
auth_basic_user_file /etc/nginx/conf/pass_file;
proxy_pass http://172.17.0.2:8501/;
Streamlit的替代品
Streamlit最大的競爭敵手主要是Plotly Dash,相對Streamlit目前的功能更加完善,但是學習曲線相比Streamlit會稍微高一些。但從整體上對Streamlit的前景會更看好些。主要是Plotly Dash把其主要限制在了Plot.ly。兩者在Github上的表現如下:

關於Plotly Dash的更多資料,期待下次有時間可以做更多的系統化整理。
參考資料: