Streamlit 项目知识点总结
目录
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()
效果如下:
如有疑问,可以关注 我的知识库,直接提问即可。