目錄
1. 單選框、下拉框格式化
2. 多媒體資源的引用
2.1 搭建一個簡易的http服務器
2.2 約定多媒體資源的輸入格式
2.3 解析多媒體資源
3. 設置頁面的全局背景圖片
4. 輸出流式文本(類似打字效果)
4.1 使用內置的 st.write_stream 方法實現
4.2 使用內置的 st.write 方法實現
5. 分頁導航
6. 使用模板渲染頁面
7. 多頁面方案
7.1 創建一個導航文件
7.2 定義每個頁面的內容
7.3 渲染導航
????????
????????最近使用python的streamlit庫做了個簡單的小項目,過程中碰到了不少問題,現總結如下。
1. 單選框、下拉框格式化
????????默認情況下,單選框和下拉框的標簽文本就是 options 參數中的列表數據,但在大多數的實際應用場景下,我們不僅需要在頁面上顯示標簽文本,同時還需要傳遞它們對應的id索引,這個時候,我們就需要對單選框和下拉框的標簽進行格式化處理。以下是單選框格式化的示例代碼:
# 使用id字段作為索引
option_list = [data.id for data in data_list]
# 使用title字段作為標簽文本
label_list = [data.title for data in data_list]
note_id = st.sidebar.radio('', option_list, format_func=lambda item: label_list[option_list.index(item)], label_visibility='collapsed')
if note_id:# 將id參數傳遞給其他業務邏輯
????????提示:該種方式同樣適用于下拉框的數據處理。
2. 多媒體資源的引用
????????streamlit中有內置的方法可以顯示圖片、音頻和視頻等多媒體資源,但請注意,這些方法都是獨立的,無法同時輸出圖片和音視頻混合的富文本信息。假如現在有一段文本需要輸出,這段文本中不僅包含了文字,同時還包含了圖片、音視頻等信息,對于這樣的場景需求,streamlit中的st.html()方法似乎可以實現,但還不夠完美,因為st.html()方法對多媒體資源的引用格式,通常都是http(s)協議的url路徑,所以如果能有一個http文件服務器,將streamlit應用中的本地資源,映射成為http資源,那就能完美解決這樣的場景需求了。具體實施步驟如下:
2.1 搭建一個簡易的http服務器
????????基于python的http.server模塊,我們可快速搭建一個簡易的http服務器。
2.2 約定多媒體資源的輸入格式
????????在streamlit應用中,約定好多媒體資源的輸入格式,示例格式如下:
圖片格式:![圖片描述][圖片地址],圖片描述可以為空
音頻格式:![audio][音頻地址],audio為固定關鍵詞
視頻格式:![video][視頻地址],video為固定關鍵詞
2.3 解析多媒體資源
????????根據約定好的格式,將多媒體資源解析出來,并映射到http文件資源,如下:
import re
import streamlit as stcontent = "我是一段包含了文字、圖片和音視頻的富文本"# 替換圖片,其中st.secrets.httpd.serverUrl是應用中配置的http文件服務器地址
image_pattern = r'!\[(.*)\]\[(.*?)\]'
placeholder = r'<div><img src="{}\2" alt="\1" title="\1" style="max-width: 100%; border-radius: 5px;"/></div>'.format(st.secrets.httpd.serverUrl)
content = re.sub(image_pattern, placeholder, content)# 替換音頻
audio_pattern = r'!\[audio\]\[(.*?)\]'
placeholder = r'<div><audio src="{}\1" style="max-width: 100%; border-radius: 5px;" controls>您的瀏覽器不支持該元素</audio></div>'.format(st.secrets.httpd.serverUrl)
content = re.sub(audio_pattern, placeholder, content)# 替換視頻
video_pattern = r'!\[video\]\[(.*?)\]'
placeholder = r'<div><video src="{}\1" style="max-width: 100%; border-radius: 5px;" controls>您的瀏覽器不支持該元素</video></div>'.format(st.secrets.httpd.serverUrl)
content = re.sub(video_pattern, placeholder, content)# 最后輸出
st.html(content)
3. 設置頁面的全局背景圖片
????????背景圖片可以是本地文件,也可以是http網絡圖片,如果是本地圖片,必須將它轉換為base64字符串,否則無法正常顯示。
????????核心代碼就一條語句,如下:
import streamlit as stst.html(f'''<style>.stApp:before{{background: url({background_image}) no-repeat fixed;background-size: cover;z-index: 1000000;pointer-events: none;position: absolute;width: 100%;height: 100%;content: "";opacity: 0.2;}}</style>
''')
????????其中的background_image是我們需要傳遞的參數,參數值就是我們前面提到的,要么是http圖片路徑,要么是圖片的base64編碼字符串。
????????下面附一個圖片轉base64字符串的方法:
import base64
import streamlit as stimage_path = '本地圖片路徑'
with open(image_path, 'rb') as file:encoded_string = base64.b64encode(file.read())
data = encoded_string.decode('utf-8')
background_image = 'data:image/png;base64,{}'.format(data)
4. 輸出流式文本(類似打字效果)
4.1 使用內置的 st.write_stream 方法實現
import time
import streamlit as st# 將字符串轉換為字符生成器
def split_text(string):for char in string:yield chartime.sleep(0.1)# 建議將輸出文本放在三個引號內,以保持原有格式
text = '''想和你說,今天的云和你,都十分可愛。\n我把喜歡寫進風里,從此整個世界都是你。
'''st.write_stream(split_text(text))
4.2 使用內置的 st.write 方法實現
????????st.write 方法更加靈活,可以根據需要定制我們任何想要的輸出。先封裝一個工具類,如下:
import time
import streamlit as stclass StreamlitUtil():'''將字符串轉換為字符生成器@string: 字符串'''@staticmethoddef split_text(string):for char in string:yield chartime.sleep(0.1)'''輸出流式字符串(類似打字效果)@obj: 字符串、字符串列表或者字符串生成器@container: 容器對象,默認為st,也可以是其它值,如:st.sidebar等。'''@staticmethoddef write_stream(obj, container=st):if isinstance(obj, str):container.write(StreamlitUtil.split_text(obj))returnif isinstance(obj, list):for item in obj:container.write(StreamlitUtil.split_text(item))returncontainer.write(obj)'''輸出流式聊天信息(類似打字效果)@message_list: 聊天信息列表,格式 [{'用戶名稱1': '內容1'}, {'用戶名稱2': '內容2'} ...]'''@staticmethoddef write_stream_chat(message_list):for message_dict in message_list:# 注意:popitem()方法會移除最后插入的鍵值對name, message = message_dict.popitem()st.chat_message(name).write(StreamlitUtil.split_text(message))
????????構造數據并調用:
from streamlit_util import StreamlitUtil as sudef run():text = '想和你說,今天的云和你,都十分可愛。'# 構造字符串列表并以流式輸出string_list = []for index in range(3):string_list.append(f'{index+1}: {text}')su.write_stream(string_list)# 構造聊天信息列表并以流式輸出string_list = []for index in range(3):string_list.append({'user': f'{index+1}: {text}'})su.write_stream_chat(string_list)run()
5. 分頁導航
????????分頁導航在web應用中是一個非常常見的功能,當有大量數據需要在頁面上渲染時,考慮到瀏覽器頁面的承受能力和后臺服務器的壓力,通常會對數據作分頁處理。
????????注意:此處只介紹如何實現分頁導航的功能,不討論分頁的數據如何獲取。
????????一個完整的分頁導航主要包含兩個部分:分頁按鈕和按鈕事件。
????????先來看下如何渲染一個分頁導航:
'''
顯示分頁導航欄
@container: 容器對象,即分頁導航欄顯示在什么地方
@total_page: 總頁數
'''
def show_page_nav(container, total_page=1):if total_page < 1:total_page = 1page_nav = {'first_page': '首頁','prev_page': '上頁','next_page': '下頁','last_page': '尾頁'}index = 0cols = container.columns(len(page_nav))for key in page_nav:with cols[index]:st.button(page_nav[key], on_click=change_page_state, args=(key, total_page))index += 1current_page = su.get_session('current_page', 1)container.caption('#### 第 {} 頁 / 共 {} 頁'.format(current_page, total_page))
????????show_page_nav 函數中,我們定義了分頁導航中包含了哪些按鈕,并從session中獲取了當前狀態是第幾頁。同時我們還注意到,在定義分頁按鈕的時候,我們綁定了一個單擊事件函數,這個函數的作用就是保存當前狀態的頁碼,它的具體內容如下:
'''
分頁按鈕的業務邏輯處理,通過session來存儲當前頁信息
@page_button_name: 分頁按鈕名稱
@total_page: 總頁數
'''
def change_page_state(page_button_name, total_page):if page_button_name == 'first_page':su.set_session('current_page', 1)returnif page_button_name == 'prev_page':current_page = su.get_session('current_page', 1)if current_page > 1:su.set_session('current_page', current_page - 1)else:su.set_session('current_page', 1)returnif page_button_name == 'next_page':current_page = su.get_session('current_page', 1)if current_page >= total_page:su.set_session('current_page', total_page)else:su.set_session('current_page', current_page + 1)returnif page_button_name == 'last_page':su.set_session('current_page', total_page)return
????????change_page_state 函數中,我們針對每個按鈕的單擊事件分別進行了處理,處理的最終目的是為了確認當前狀態的頁碼,并將這個頁碼存儲在session中。
????????我們發現,以上代碼只是渲染了一個分頁導航欄,并將按鈕點擊時的當前頁碼保存在了session中,似乎并沒有關聯其它具體的業務邏輯,那么如何將它整合到具體的業務邏輯中呢?其實,關鍵就在于session,通過session我們可以共享數據,這就意味著,在具體的業務邏輯中,我們只需要將當前頁碼作為參數傳遞給獲取數據的函數即可。
6. 使用模板渲染頁面
????????我們知道,streamlit中內置了各種輸出函數,可以將我們想要展示的內容渲染到頁面上,其中,有一個st.html()函數,為我們渲染豐富多彩的頁面提供了無限可能。
????????試想下,如果要渲染一段樣式豐富的文本,我們會怎么做呢?最容易想到的就是直接在python代碼中定義好樣式和內容,并將他們通過st.html()函數進行輸出。這樣做,從功能實現上來說沒問題,但由于我們將樣式和內容混在了一起,尤其當樣式很多的時候,對于代碼的后期維護將會是一個災難。這個時候,你可能就會想,那如果將樣式和內容分離呢?好注意,那么就讓我們來看下,如何實現樣式和內容的分離。
????????首先,定義一個html文件模板,示例內容如下:
<div style="text-align: center;"><div style="font-weight: bold;">{title}</div><div style="color: #999999;">{publish_time}</div>
</div>
<div style="margin-top: 10px;">{content}
</div>
????????我們看到,模板文件中,除了html標簽和樣式,我們還定義了幾個變量,這些變量用花括號引起來,我們姑且稱它們為占位符。
????????下面,我們再來看下如何引用這個模板,并使用其中的占位符:
# 定義模板文件路徑
template = 'page/template/content.html'
# 讀取模板文件內容,并替換其中的占位符變量
with open(template, 'r') as file:content = file.read()content = content.replace('{title}', '厲害了我的國')content = content.replace('{publish_time}', '2024-05-31')content = content.replace('{content}', '這里是一大段內容...')# 輸出內容到頁面st.html(content)
????????怎么樣?這樣的代碼看起來是不是很清爽干凈,后期的維護也會簡單輕松很多。
7. 多頁面方案
????????從1.36.0版本開始,streamlit 啟用了新的多頁面方案,使用起來更加簡單、靈活。
????????導航的定義建議使用字典格式,如下:
pages = {'一級導航1' : [st.Page('py文件路徑', title='📘 二級導航1', url_path='url路徑,用于顯示在地址欄中', default=True),st.Page('py文件路徑', title='📋? 二級導航2', url_path='url路徑,用于顯示在地址欄中', default=False)],'一級導航2' : [st.Page('py文件路徑', title='🃏 二級導航3', url_path='url路徑,用于顯示在地址欄中', default=False),st.Page('py文件路徑', title='🎁 二級導航4', url_path='url路徑,用于顯示在地址欄中', default=False)]
}
????????屬性說明:
????????第一個參數:導航頁面對應的py文件路徑,建議使用相對路徑。
????????title:二級導航的名稱。
????????url_path:導航的url路徑,用于顯示在瀏覽器的地址欄中,如果未定義該參數,則默認使用py文件名稱(去除.py后綴)作為url路徑。
????????default:是否作為應用的默認頁面,默認為 False,默認情況下,取 st.navigation 方法參數中的第一個頁面作為應用的首頁。
????????下面來演示一個簡單的多頁面應用。
7.1 創建一個導航文件
????????創建一個 router.py 文件,項目中所有的導航頁面都存放在這個文件中,內容如下:
import streamlit as stdef load_router():pages = {'🏠? 導航名稱1' : [st.Page('pages/front/測試1.py', title='📘 子導航1'),st.Page('pages/front/測試2.py', title='📋? 子導航2')],'?? 導航名稱2' : [st.Page('pages/admin/測試3.py', title='🃏 子導航3'),st.Page('pages/admin/測試4.py', title='🎁 子導航4')]}return pages
7.2 定義每個頁面的內容
????????以 測試1.py 文件為例,它的內容如下:
import streamlit as stdef run():st.write('欲買桂花同載酒')run()
7.3 渲染導航
import streamlit as st
import router as navdef run():st.set_page_config(layout='wide', page_title='導航示例')pg = st.navigation(nav.load_router())pg.run()run()
????????效果如下:
?????????如有疑問,可以關注 我的知識庫,直接提問即可。