在 Streamlit 和 Asyncio 中提取实时数据并更新

DLW*_*DLW 7 python python-asyncio streamlit

目标是在后台提取实时数据(例如每 5 秒)并在需要时提取到仪表板。这是我的代码。它有点工作,但我看到两个问题: 1.如果我将 st.write("TESTING!") 移到最后,由于 while 循环,它永远不会被执行。有办法改进吗?我可以想象,随着仪表板的增长,将会有多个页面/表格等。这不会提供太多的灵活性。2. async 函数中的 return px 行,我对此不太满意,因为我通过反复试验得到了正确的结果。抱歉,我是个新手,但如果有更好的方法,我将非常感激。

谢谢你!

import asyncio
import streamlit as st
import numpy as np

st.set_page_config(layout="wide")

async def data_generator(test):
    while True:
        with test:
            px = np.random.randn(5, 1)
        await asyncio.sleep(1)
        return px

test = st.empty()
st.write("TESTING!")

with test:
    while True:
        px = asyncio.run(data_generator(test))
        st.write(px[0])
Run Code Online (Sandbox Code Playgroud)

HHe*_*est 6

根据我的经验,使用 asyncio 的技巧是提前创建布局,在需要显示异步信息的地方使用空小部件。异步协程将接收这些空槽并填充它们。这应该可以帮助您创建更复杂的应用程序。

然后 asyncio.run 命令可以成为最后执行的 Streamlit 操作。正如您所观察到的,此后的任何 Streamlit 命令都不会被处理。

我还建议在初始布局期间在异步函数之外安排任​​何输入小部件,然后发送小部件输出进行处理。当然,您可以在函数内绘制输入小部件,但布局可能会变得很棘手。

如果您仍然希望将输入小部件放在异步函数中,那么您肯定必须将它们放在 while 循环之外,否则您会收到重复的小部件错误。(您可能会尝试通过始终创建新的小部件来克服这个问题,但是输入小部件将被“重置”并且无法实现交互,更不用说可能的内存问题了。)

这是我的意思的完整示例:

import asyncio
import pandas as pd
import plotly.express as px

import streamlit as st
from datetime import datetime


CHOICES = [1, 2, 3]


def main():
    print('\nmain...')

    # layout your app beforehand, with st.empty
    # for the widgets that the async function would populate
    graph = st.empty()
    radio = st.radio('Choose', CHOICES, horizontal=True)
    table = st.empty()
    
    try:
        # async run the draw function, sending in all the
        # widgets it needs to use/populate
        asyncio.run(draw_async(radio, graph, table))
    except Exception as e:
        print(f'error...{type(e)}')
        raise
    finally:    
        # some additional code to handle user clicking stop
        print('finally')
        # this doesn't actually get called, I think :(
        table.write('User clicked stop!')
    
    
async def draw_async(choice, graph, table):
    # must send in all the streamlit widgets that
    # this fn would interact with...

    # this could possibly work, but layout is tricky
    # choice2 = st.radio('Choose 2', CHOICES)

    while True:
        # this would not work because you'd be creating duplicated
        # radio widgets
        # choice3 = st.radio('Choose 3', CHOICES)

        timestamp = datetime.now()
        sec = timestamp.second

        graph_df = pd.DataFrame({
            'x': [0, 1, 2],
            'y': [max(CHOICES), choice, choice*sec/60.0],
            'color': ['max', 'current', 'ticking']
        })

        df = pd.DataFrame({
            'choice': CHOICES,
            'current_choice': len(CHOICES)*[choice],
            'time': len(CHOICES)*[timestamp]
        })

        graph.plotly_chart(px.bar(graph_df, x='x', y='y', color='color'))
        table.dataframe(df)

        _ = await asyncio.sleep(1)


if __name__ == '__main__':
    main()
Run Code Online (Sandbox Code Playgroud)