SetupTools(三)
项目主体
基于 Streamlit 进行可视化,默认端口 8501.
基本运行
直接运行
采用了声明式的网页描述范式,所见即所得。
1 | streamlit run Index.py |
脚本模式
脚本模式下便于 PyInstaller 封装。需要一个 .py 脚本作为项目入口。
说明如下。
- .streamlit/config.toml
端口配置等,可加可不加,但为了避免打包后出现奇怪告警和subpages找不到等问题,建议加上 - hooks/
用于打包的钩子文件 - run_index.py
封装 index.py,作为 Streamlit 运行脚本和 PyInstaller 打包入口 - run_index.spec
打包配置项
目录说明
config.toml
1 | [server] |
不过也发现,打包为单文件后 .streamlit/ 目录找不到,即便在 __MEIPASS
下搜索也不行。所以在 run_index.py 中实际上是通过 sys.argv 传入配置,而非 config.toml.
hooks/hook_streamlit.py
按照常见实现即可。
run_index.py
用于脚本模式拉起 Index.py,启动命令为
1 | python3 run_index.py |
这样做还有一个好处,在 dev 模式下,不需要为每个文件都导一遍包源码的路径,只需在 run_index.py 导入即可;这因为 run_index 生命周期覆盖了所有 pages 的生命周期。
关于 run_index.py
首先明确 PyInstaller 对 Streamlit 打包后的最简结构。
- .exe
- Index.py
- pages/
- .streamlit/
页面文件和配置文件无法打包进应用程序。Streamlit 需要在当前的执行路径 sys.getcwd
下,并且还要让 .exe 能够找到页面文件。
如果只是打包为单个文件夹即 --onefolder
,这一步已经够了,只需在 Electron Builder 中打包生成的整个 folder 即可。
如果希望继续打包为单文件,则有如下方式。
Enigma Virtual Box
该工具提供了一个虚拟文件层,可以将任意文件或目录结构打包成单个可执行文件。可以将上一节提到的目录结构打包为单个文件,但运行时的性能问题严重。推测是动态语言打包成可执行文件时,访存压力较大,虚拟文件层成为性能瓶颈。
继续通过 PyInstaller 打包
打包器支持打包 python 文件以外的数据和配置文件,并且支持指定打包后的相对路径。
在 .spec 中进行如下配置:
1 | datas += [('Index.py', '.')] |
二元组的首个元素是待打包文件相对于 .spec 的文件路径,第二个元素是项目运行后,期待该文件所在的目录路径。
但实际打包后,还需要一步查找临时解压目录的步骤。这里要提到 PyInstaller 打包单文件的运行逻辑,是将各种文件解压到临时目录,在这个过程中,数据文件的路径会改变,直接使用 .spec 指定的相对路径无法找到文件。
明确这一点后便可以通过动态切换 run_index 执行路径来找到数据文件。又发现即使切换到临时目录,config.toml 仍然不生效,所以采用 sys.argv
传参方式传入配置。
最终的 run_index 如下。
1 | import streamlit.web.cli as stcli |
至此,解决了入口文件问题。一份文件可以同时支持本地脚本运行与 PyInstaller 打包两种模式,打包后的 .exe 可放在任意路径下。
关于 run_index.spec
可以直接把 python 文件打包成 exe,也可以经由 spec 文件进行个性化配置。
生成 .spec 文件:
1 | pyinstaller --onefile --additional-hooks-dir=./hooks Index.py --clean |
以下列出了 .spec 文件需要添加或修改的部分。
1 | # 打包运行库 |
EXE 部分,配置 name / debug / comsole 三个属性即可。
最后使用 .spec 生成 .exe
1 | pyinstaller run_index.py --clean |
踩坑
os
模块找不到波浪号目录。请使用专门的用户目录定位命令。