Streamlit 快速構建互動式頁面的python庫

2023-11-23 21:00:51

基礎介紹

streamlit 是什麼

Streamlit是一個面向機器學習和資料科學團隊的開源應用程式框架,通過它可以用python程式碼方便快捷的構建互動式前端頁面。streamlit特別適合結合大模型快速的構建一些對話式的應用,可以看到一些行業內熱門的使用。


專案本身也比較成熟,release版本,start數量等都表明該專案持續打磨了很長時間。

streamlit 簡單範例

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

st.title('Sin')

# Get user input for frequency and amplitude
freq = st.slider('頻率', min_value=1, max_value=10, value=1)

# Create x values
x = np.linspace(0, 2*np.pi, 1000)

# Create y values
y = np.sin(freq * x)

# Plot the graph
fig, ax = plt.subplots()
ax.plot(x, y)
ax.set_title('Sine Wave')
ax.set_xlabel('X')
ax.set_ylabel('Y')
st.pyplot(fig)

執行程式碼

streanlit run sample_demo.py

streamlit 優缺點

streamlit優點:

  • 不需要掌握前端知識就能建立web頁面
  • 內建很多機器學習互動的展示元件,更有利於演演算法工程師使用
  • 開發速度快,修改方便
    streamlit缺點:
  • 前端介面固定,開發者不能隨意調整控制元件位置
  • 只適合於少量頁面的專案,不適用於複雜網頁的網站前端

元件集合

streamlit 主要用於構建前端頁面,有著豐富的前端元件。 streamlit 官方檔案:​​https://docs.streamlit.io/library/get-started/main-concepts​​

從官方檔案來看,主要的元件包括:

  1. 文字
  2. 資料表格
  3. 圖表
  4. 輸入元件
  5. 媒體元件
  6. 佈局和容器
  7. 聊天框
  8. 狀態展示
  9. 控制流程

下面將常見的元件拿出來,做一個集合。stremlit的一個頁面叫做一個app,可以將多個頁面組裝起來,如下圖:

文字

資料表格

輸入元件

聊天

js和html 渲染

上文中已經提到streamlit的元件排列方式是從上到下逐個渲染,無法做到html那樣靈活的調整元件的位置。但是stream還是提供了兩個函數可以支援對頁面css和js的修改。

對css的修改

streamlit中按鈕是沒有背景顏色的,如果想增加按鈕的底色,就可以對其css修改。如下:

import streamlit as st

st.button("點選我")

hide_streamlit_style = """
       <style>
       .ef3psqc11 {background-color: yellowgreen}
       </style>
       """
st.markdown(hide_streamlit_style, unsafe_allow_html=True)

通過類名找到對應的按鈕,然後準備好css程式碼,最後使用st.markdown函數將css渲染到頁面上。

這時就可以通過css 隱藏屬性來完成。根據按鈕的id找到按鈕,隱藏該元件。

hide_streamlit_style = """
        <style>
        #MainMenu {visibility: hidden;}
        footer {visibility: hidden;}
        </style>
        """
st.markdown(hide_streamlit_style, unsafe_allow_html=True)

插入js
除了可以插入css之後,streamlit也支援插入js程式碼,這個功能就賦予streamlit操作html頁面的能力。上面修改按鈕顏色的需求通過js也能實現。首先通過js找到按鈕,然後對元素的屬性賦值。

import streamlit as st
import streamlit.components.v1 as components

st.button("點選我")

# hide_streamlit_style = """
#        <style>
#        .ef3psqc11 {background-color: yellowgreen}
#        </style>
#        """
# st.markdown(hide_streamlit_style, unsafe_allow_html=True)



js_btn = '''window.parent.document.getElementsByClassName("ef3psqc11")[0].style.backgroundColor = "bisque"'''
components.html(f'''<script>{js_btn}</script>''', width=0, height=0)

修改js使用的是import streamlit.components.v1 as components,和普通js不同的是需要在js前面加上window.parent,否則不能生效。
除了支援原生js之後,也支援jquery庫。

元件工作原理

使用streamlit構建的頁面和html構建的頁面在工作方式上有很大的不同,streamlit有自己的一套工作機制,具體來說有如下兩點注意:

  1. streamlit 根據元件在程式碼的位置,從上至下渲染元件
  2. 點選或觸發某一個控制元件之後,程式碼會從上至下執行一遍

對談狀態

在前文中提到streamlit中觸發一個按鈕會重新執行整個檔案,相當於整個程式碼重新執行一遍,這樣帶來的一個副作用就是前後兩次操作不能互相傳遞資料。比如下圖中想要實現點選確定按鈕,將輸入的內容展示在最上面。

這個簡單的需求反而不好實現,因為點選確定按鈕之後,整個程式重新執行,輸入的使用者名稱和密碼已經是上一次頁面的資料,無法傳遞到下一次頁面渲染中。這時就需要對談狀態來解決這個問題了。
首先解釋一下streamlit中的對談。在streamlit中一個tab頁表示一個對談,新開tab頁或者重新整理頁面都代表對談失效。對談機制提供了一種能力:在對談中,也就是一個tab中存在一個全域性物件,支援插入、更新、刪除資料,該物件在對談任何時機都可以使用。對談機制可以有效解決頁面渲染前後帶來的資料傳遞問題。
下面看看通過對談機制如何解決資料傳遞

import streamlit as st

msg = ""
if "name" in st.session_state:
    msg = {"name": st.session_state.name, "passwd": st.session_state.passwd}

st.write(f"輸入的內容是:{msg}")
name = st.text_input(label="使用者名稱:")
passwd = st.text_input(label="密碼:")
submit = st.button("確定")
if submit:
    st.session_state["name"] = name
    st.session_state["passwd"] = passwd
streamlit run text_input.py 

st.session_state 就是對談機制的全域性變數,在按鈕點選之後向st.session_state中更新資料,當新一輪迴圈開始時判斷st.session_state中是否有name屬性,如果存在name資料表明對談中有資料,讀取資料展示出來。
st.session_state支援即支援字典的資料管理方式,也支援屬性的管理方式。也就是說獲取一個資料,st.session_state["data"] 和 st.session_state.data都是支援的

避坑指南

在實際使用streamlit中也有到一些讓人困惑的事情,下面列舉出來避免有人同樣踩坑。

不同按鈕的監聽方式會影響元件渲染順序

按鈕有兩種監聽方式,分別是監聽按鈕變數和繫結回撥函數

# 按鈕監聽方法1
submit = st.button()
if submit:
    pass 


# 按鈕監聽方法2
submit = st.button(on_click=handler_click)

這兩種監聽方式有不同的元件渲染順序。具體來說是:

  1. 使用監聽按鈕方式,點選按鈕之後程式重新執行所有程式碼,執行到按鈕時進入監聽程式碼片段
  2. 使用回撥函數,點選按鈕之後先執行回撥函數,再重新執行所有程式碼

如下獲取輸入框資訊,就會發現獲取的是button出現時input輸入框的狀態,而不是最新的狀態

import streamlit as st

def get_username_passwd(username, password):
    st.write("username:", username, "password:", password)


with st.form("登入頁面"):
    username = st.text_input("使用者名稱")
    password = st.text_input("密碼")

    # 使用on_click的方法,獲取的是button出現時input輸入框的狀態,而不是最新的狀態。逆天bug
    submitted = st.form_submit_button("登入", on_click=get_username_passwd, args=(username, password))

元素沒有固定ID

在streamlit中生成的頁面沒有固定的class name 或 id。在不同環境下可能生成不一樣的class name。所以通過js或css修改頁面的方法往往不能通用,因為類名會發生變化。
可靠的獲取元素的方法是使用 data-testid="stFormSubmitButton"中的 data_testid。

但是如果頁面中有多個相同屬性的 data-testid,那麼這種方式也不可靠。

使用建議

來自三體人的吶喊:
不要在大型專案中使用!
不要在大型專案中使用!
不要在大型專案中使用!

streamlit調整佈局是一件痛苦的事情。個人真實感受:如果說做專案想蓋房子,做普通專案是用水泥磚頭蓋房子,用streamlit像是用積木蓋房子,感覺碰一下就倒了。

streamlit更適合用在功能單一,頁面較少,沒有頁面跳轉的專案上。各種封裝好的元件能夠快速實現一個最小可行性產品(MVP),避免演演算法工程師在前端頁面花費太長時間。