资讯详情

在 Streamlit 中嵌入 D-Tale(Flask 应用):`dtale-streamlit` 的实现原理与实战指南

📅 2026/10/13 6:46:08 | 华诺云谱 👁 阅读
在 Streamlit 中嵌入 D-Tale(Flask 应用):`dtale-streamlit` 的实现原理与实战指南
数据分析数据可视化后端前端【免费下载链接】dtaleVisualizer for pandas data structures项目地址https://gitcode.com/gh_mirrors/dt/dtale点击查看免费下载导读本文讲解如何在 Streamlit 应用中嵌入 D-Tale乃至任意 Flask 应用的完整方案。你将掌握一条由“覆写 Streamlit Tornado 服务器的start_listening函数 WSGIContainer/FallbackHandler桥接”构成的成熟技术路线并学会使用 D-Tale 官方提供的dtale-streamlit命令直接运行脚本、通过startup()/get_instance()在 Streamlit 与 D-Tale 之间传递 DataFrame以及用iframe把 D-Tale 网格、图表和代码导出面板渲染进 Streamlit 页面。文中所有原理均可对照本仓库的 dtale/cli/streamlit_script.py、setup.py 与 tests/dtale/test_cli.py 逐一验证。一、背景Streamlit 的 Web 服务器为什么需要“黑客手段”Streamlit 是一个用 Python 快速搭建网页应用的工具它底层使用的是 Tornado 的tornado.httpserver.HTTPServer。而 D-Tale 本身是一个基于 Flask 的应用核心构建逻辑见 dtale/app.py 中的build_app()。如果两个应用各自监听一个端口就需要用户在两个页面之间来回切换体验割裂理想的方式是让 D-Tale 的 Flask 路由“寄生”在 Streamlit 同一个 Tornado 服务器上共用端口与域名。在 docs/EMBEDDED_DJANGO.md 中我们已经完成了 D-Tale 嵌入 Django 的实践这套经验完全可以迁移到 Streamlit关键在于找到 Streamlit 服务器构建路由的地方再把 Flask 应用挂进去。1.1 突破口streamlit.server.Server._create_app()通过阅读 Streamlit 源码可以发现streamlit.server.Server中存在一个_create_app()函数它负责为每个 Streamlit 服务器构建不同的路由这些路由最终被组装进tornado.web.Application。既然 Streamlit 的底层就是 Tornado 的Application那么问题就转化为如何把一个 Flask 应用并入 Tornado 的tornado.web.Application路由表中1.2 桥接方案WSGIContainerFallbackHandlerTornado 官方提供了一条桥接路径用tornado.wsgi.WSGIContainer把任意 WSGI 应用Flask 应用天然满足 WSGI 规范包装成 Tornado 可以调用的对象再用tornado.web.FallbackHandler作为“兜底处理器”把请求转交给它。一个经典的示例骨架如下from tornado.wsgi import WSGIContainer from tornado.ioloop import IOLoop from tornado.web import FallbackHandler, RequestHandler, Application from flasky import app class MainHandler(RequestHandler): def get(self): self.write(This message comes from Tornado ^_^) tr WSGIContainer(app) application Application([ (r/tornado, MainHandler), (r.*, FallbackHandler, dict(fallbacktr)), ]) if __name__ __main__: application.listen(8000) IOLoop.instance().start()这段代码展示了核心思想Tornado 优先匹配自定义的MainHandler路由其余所有请求r.*都落入FallbackHandler转交给 WSGIContainer 包装的 Flask 应用。1.3 找到可覆写的钩子start_listening既然 Streamlit 的_create_app()也用tornado.web.Application剩下的问题就是如何在 Streamlit 启动时把我们的FallbackHandler追加进它的路由表。观察streamlit.server.server.Server.start中这两行app self._create_app() start_listening(app)start_listening是一个公开函数——公开意味着它可以在 Python 中被“偷梁换柱”monkey-patch。于是思路清晰了覆写start_listening在它被调用时先把 Flask 应用挂进 Tornado 路由再调用原始的start_listening完成 Streamlit 自身的监听。二、核心原理覆写start_listening的三步曲最初的探索代码即 docs/EMBEDDED_STREAMLIT.md 中记录的原型如下import streamlit.server.server as streamlit_server from streamlit.cli import main from tornado.wsgi import WSGIContainer from tornado.web import FallbackHandler from dtale.app import build_app orig_start_listening streamlit_server.start_listening def _override_start_listening(app): dtale_app_obj build_app(reaper_onFalse) tr WSGIContainer(dtale_app_obj) app.add_handlers(r.*, [(.*dtale.*, FallbackHandler, dict(fallbacktr))]) orig_start_listening(app) streamlit_server.start_listening _override_start_listening对这段代码逐行拆解保存原始函数orig_start_listening streamlit_server.start_listening为稍后“先挂路由、再正常监听”做准备构建 Flask 应用调用dtale.app.build_app(reaper_onFalse)。注意reaper_onFalse——D-Tale 默认会启动一个“自动收割reaper”子进程在一小时无活动后关闭应用见 dtale/app.py 中REAPER_TIMEOUT与build_reaper的实现在嵌入 Streamlit 的场景下进程生命周期由 Streamlit 托管必须关闭这个自带的重启/关闭机制注册路由app.add_handlers(r.*, [(.*dtale.*, FallbackHandler, dict(fallbacktr))])将 D-Tale 应用以FallbackHandler形式挂进 Tornado 的Application。2.1 路由匹配器的两个关键约束原文特别强调了两点容易踩坑的约束它们至今仍是正确嵌入的准绳匹配器不能过松如果匹配模式写成.*会吞掉 Streamlit 自身的核心路由导致整个 Streamlit 应用不可用匹配器要覆盖所有 Flask 路由.*dtale.*只匹配包含dtale的路径如果 D-Tale 应用中存在不包含该关键字的其他路由例如/、/favicon.ico、/health等参见 dtale/app.py 中build_app()注册的根路由这些路由将不会被转发到 Flask 应用。若你的 Flask 应用路由命名五花八门需要相应调整正则让所有业务路径都能命中 FallbackHandler。三、仓库内的生产级实现dtale/cli/streamlit_script.py上文原型最终被固化进 D-Tale 仓库成为官方入口模块 dtale/cli/streamlit_script.py。与早期原型相比它针对 Streamlit 的版本演进做了兼容处理try: import streamlit.server.server as streamlit_server except BaseException: # ImportError, ModuleNotFoundError import streamlit.web.server.server as streamlit_server import sys try: from streamlit.cli import main except BaseException: from streamlit.web.cli import main from tornado.wsgi import WSGIContainer from tornado.web import FallbackHandler from dtale.app import build_app from dtale.cli.clickutils import run orig_start_listening streamlit_server.start_listening def _override_start_listening(app): dtale_app_obj build_app(reaper_onFalse) tr WSGIContainer(dtale_app_obj) app.add_handlers(r.*, [(.*dtale.*, FallbackHandler, dict(fallbacktr))]) orig_start_listening(app) streamlit_server.start_listening _override_start_listening # we need to stop XSRF protection since dash wont work otherwise additional_args [] if --server.enableCORS not in sys.argv: additional_args [--server.enableCORS, false] if --server.enableXsrfProtection not in sys.argv: additional_args [--server.enableXsrfProtection, false] sys.argv additional_args if __name__ __main__: run(main)生产实现相对原型的差异与原因双路 import 兜底旧版 Streamlit 把server与cli放在streamlit.server.server/streamlit.cli新版本迁移到了streamlit.web.server.server/streamlit.web.cli。仓库用try/except BaseException依次尝试兼容两类版本命令行包装通过dtale.cli.clickutils.run(main)执行 Streamlit 的 CLIrun的实现在 dtale/cli/clickutils.py负责整理参数顺序、打印版本与耗时日志、统一异常退出码XSRF/CORS 的自动处理见下文第四节。3.1 入口注册setup.py中的console_scripts为了让用户能以命令行方式调用D-Tale 在 setup.py 中注册了两个 console_scripts 入口entry_points{ console_scripts: [ dtale dtale.cli.script:main, dtale-streamlit dtale.cli.streamlit_script:main, ] }这样安装 D-Tale 后就可以像使用streamlit命令一样用带dtale-前缀的命令启动dtale-streamlit run my_script.py同时setup.py 还提供了streamlit安装 extrastreamlit: [streamlit]一键安装时即可带上 Streamlit 依赖。3.2 测试佐证仓库测试 tests/dtale/test_cli.py 中的test_streamlit用例直接验证了这一机制的行为模拟dtale.app.build_app断言其以reaper_onFalse被调用分别对streamlit.server.server.start_listening与streamlit.web.server.server.start_listening打桩同样体现了新旧版本兼容策略断言sys.argv末尾被追加了[--server.enableCORS, false, --server.enableXsrfProtection, false]。四、EXTRA HACK为什么必须关闭 XSRF 防护与 CORS这是原文档中重点标注的“额外黑客手段”也是 D-Tale 嵌入 Streamlit 能否正常工作的关键D-Tale 的图表模块基于 Plotly Dash而 Dash 大量使用POST 请求当 Streamlit 默认开启server.enableXsrfProtectionXSRF 跨站请求伪造防护时这些 POST 请求会被拒绝导致 Dash 功能不可用。因此在 dtale/cli/streamlit_script.py 中模块加载时会自动向sys.argv追加两个参数additional_args [] if --server.enableCORS not in sys.argv: additional_args [--server.enableCORS, false] if --server.enableXsrfProtection not in sys.argv: additional_args [--server.enableXsrfProtection, false] sys.argv additional_args设计上做了两处防御幂等性先检查命令行里是否已经显式传入了这两个参数--server.enableCORS not in sys.argv如果用户自己指定过就尊重用户的选择不重复追加默认关闭未显式指定时默认追加--server.enableCORS false与--server.enableXsrfProtection false。如果你的嵌入方案中没有 Dash 这类依赖 POST 请求的组件可以忽略这条 hack若你的 Flask 应用同样需要 POST则这条经验直接适用。五、实战改造你的 Streamlit 脚本使用 D-Tale配合 docs/EMBEDDED_DTALE_STREAMLIT.md官方给出了从安装到改脚本的完整上手路径。5.1 安装与启动两种安装方式任选其一pip install streamlitpip install dtale[streamlit]推荐一步到位启动时不要用streamlit命令而要用 D-Tale 提供的入口dtale-streamlit run my_streamlit_script.py其余参数与streamlit run的用法一致。5.2 向 D-Tale 保存数据dtale.views.startup在 Streamlit 脚本中通过startup()把 DataFrame 注册进 D-Tale 的全局状态import pandas as pd from dtale.views import startup df pd.DataFrame([1, 2, 3, 4]) startup(data_id1, datadf)data_id是这条数据在 D-Tale 全局状态中的唯一标识startup的完整签名与参数说明见 dtale/views.py。在 Streamlit 场景下进程内全局状态dtale.global_state随 Streamlit 服务器常驻因此数据只需注册一次后续刷新页面时依然可以按data_id取回。5.3 从 D-Tale 取回数据dtale.app.get_instancefrom dtale.app import get_instance # 记住我们上一步把 data_id 设成了 1 df get_instance(1).dataget_instance(data_id)实现见 dtale/app.py 中get_instance函数返回一个DtaleData实例其.data属性就是注册时的 DataFrame。只要记住data_id就能随时拿到 D-Tale 中任意一份数据。DtaleData还提供了.view_data应用了 UI 端排序/过滤后的数据、.kill()、.cleanup()等能力见 dtale/views.py 起的类定义。5.4 前端展示在iframe中渲染 D-TaleD-Tale 的路由以/dtale/main/{data_id}为前缀DtaleData.build_main_url的实现在 dtale/views.py因此可以直接嵌进 Streamlit 页面iframe src/dtale/main/1 /这会渲染标准的 D-Tale 网格且网格上的所有 D-Tale 操作排序、过滤、编辑单元格、图表等都可以正常使用——因为请求都经由同一台 Tornado 服务器转发到了嵌入的 Flask 应用。如果需要在新标签页打开用普通链接即可a href/dtale/main/1 target_blankDataframe 1/a六、仓库内置的两个完整示例6.1 示例一改造 DataFrame 嵌入列分析面板script_columns_and_code.py 演示了三条典型操作用startup(data_id1, datadf)初始化数据并用get_instance(1)读回、刷新列清单用iframe src/dtale/main/1嵌入主网格用iframe src/dtale/popup/column-analysis/1?selectedCol{col}嵌入指定列的列分析弹窗对应后端路由dtale.route(/column-analysis/data_id)见 dtale/views.py通过requests.get(http://localhost:8501/dtale/code-export/1).json()[code]拉取 D-Tale 根据当前 UI 操作生成的 Pandas 代码展示在 Streamlit 侧边栏后端路由见 dtale/views.py。其中把生成代码的前缀替换为df pd.DataFrame([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])是为了让示例代码可直接独立运行不依赖 D-Tale 会话上下文。6.2 示例二爬取 baseball-reference.com 并动态加载数据script_baseball.py 展示了一个更贴近真实业务的数据流用户在st.text_input输入球员姓名脚本用requestsBeautifulSoup抓取 baseball-reference.com 的搜索结果带 5 次重试的load_content命中唯一球员时抛出PlayerPageRedirectException直接跳转否则用按钮让用户选择update_dtale_data(player_data)内核对数据管理 API 的典型用法def update_dtale_data(player_data): curr_data get_instance(1) if curr_data is not None: # append data to pre-existing data in D-Tale curr_data curr_data.data curr_data curr_data[~(curr_data[name] player_data[name].values[0])] player_data pd.concat([curr_data, player_data], ignore_indexTrue) cleanup(1) # load data to D-Tale startup(data_id1, dataplayer_data)这里先用cleanup(1)删除旧实例cleanup的实现见 dtale/global_state.py再startup重新注册实现“同一 data_id 下的数据替换”。动态拼接 D-Tale 图表 URL并利用uuid.uuid4().hex作为_id参数——这是 Streamlit 页面重载时强制 iframe 重新加载的常用技巧def build_chart_url(names): params { chart_type: line, x: Season, y: json.dumps([HR]), group: json.dumps([name]), group_type: groups, group_val: json.dumps([{name: name} for name in names]), } return /dtale/charts/1?{}.format(urllib.parse.urlencode(params))最后用st.markdown(html, unsafe_allow_htmlTrue)输出含自定义 CSS 的 iframe让网格与图表铺满 Streamlit 页面。七、注意事项与边界路由匹配模式app.add_handlers中.*dtale.*的正则决定了哪些 Flask 路由可用务必按需调整既不能漏掉业务路由也不能宽松到吞掉 Streamlit 自身路由reaper_onFalse嵌入场景下必须关闭 D-Tale 的自动收割机制避免其“一小时无活动即关停”的后台线程干扰 Streamlit 进程相关代码见 dtale/app.py 的build_reaper/REAPER_TIMEOUTXSRF/CORS 默认关闭这是 Dash POST 请求能正常工作的前提涉及安全性取舍时需结合部署环境评估见第四节版本兼容Streamlit 新旧版本模块路径不同streamlit.server.*与streamlit.web.*仓库实现已用try/except兜底若你在自定义嵌入脚本中复刻此方案也应保留同样的容错逻辑iframe 高度D-Tale 网格与图表默认不会自适应父容器高度两个示例脚本中都通过自定义 CSS如iframe.grid { height: 700px; }显式指定高度实际项目中需要按页面布局调整。八、总结本文完整还原了 D-Tale 嵌入 Streamlit 的技术路线从发现 Streamlit 底层是 Tornado、找到_create_app()与可覆写的start_listening()到用WSGIContainerFallbackHandler把 Flask 应用挂进 Tornado 路由表再到仓库中 dtale/cli/streamlit_script.py 的完整生产实现与 tests/dtale/test_cli.py 的测试验证。这套方案不仅适用于 D-Tale也适用于任何希望寄生在 Streamlit 服务器上的 Flask 应用。配合startup/get_instance的数据管理 API 与 iframe 渲染模式你可以在一个 Streamlit 页面里同时拥有快速 UI 开发能力和 D-Tale 的完整数据可视化能力相关思路同样可以参考 D-Tale 嵌入 Django 与 Flask 的姊妹篇文档。赞分享数据分析数据可视化后端前端【免费下载链接】dtaleVisualizer for pandas data structures项目地址https://gitcode.com/gh_mirrors/dt/dtale点击查看免费下载相关推荐在 Streamlit 中嵌入 D-Tale从 dtale-streamlit 命令到 iframe 集成实战指南在 Streamlit 中嵌入 D Tale从 dtale streamlit 命令到 iframe 集成实战指南 D Tale 是 pandas 数据结构的数据分析数据可视化后端前端GitHub_Trending/cl/claude-plugins-official插件市场推广提升插件可见度的终极策略GitHub_Trending/cl/claude plugins official插件市场推广提升插件可见度的终极策略 在当今快速发展的开发者工具生态中AI 插件开发工具插件系统Operit 的 ADB 双 applicationId 调试支持Debug/Release 双包解析、动态路径与热更新隔离Operit 的 ADB 双 applicationId 调试支持Debug/Release 双包解析、动态路径与热更新隔离 导读 本文讲解 Operit 仓数据分析数据可视化后端前端上一篇MLflow 供应链安全实践用 pip 哈希校验抵御 Python 包篡改下一篇Data Science for Beginners 排障实战指南Python、Jupyter、Quiz 应用与 Docsify 全链路问题排查手册创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑