- 前端
- 跨平台
- 桌面应用
- 移动开发
【免费下载链接】flet
Build realtime web, mobile and desktop apps in Python only. No frontend experience required.
自 Flet 0.86.0 起,flet build会在打包时默认将应用自身与已安装的 Python 包预编译为字节码(.pyc),从而消除移动端每次冷启动时的重复编译开销。本文以官方变更说明为主体,结合 flet-cli 源码与实际配置示例,完整解读这一默认行为的来龙去脉、收益原理、关闭方式与参数优先级,帮助你在升级后快速判断是否需要显式迁移。
:::note 本文基于 Flet 0.86.0 编写,后续版本可能新增 API 或提供额外的迁移路径。完整的破坏性变更索引见 breaking changes index。 :::
变更总览:编译默认开启
在 0.86.0 之前,flet build默认不编译 Python 源码,除非你显式通过--compile-app/--compile-packages命令行参数,或在pyproject.toml中配置[tool.flet.compile]主动开启。
从 0.86.0 开始,行为反转:编译成为默认动作。flet build会通过python -m compileall -b将应用源码和已安装的包预编译为.pyc字节码,并且原始.py文件会从最终打包产物中移除。这意味着最终交付的 bundle 中携带的是编译后的字节码,而不是可直接阅读的 Python 源码。
这一默认行为的转变,在 flet-cli 的构建基类中有清晰的源码佐证。查看 build_base.py:
if self.get_bool_setting(self.options.compile_app, "compile.app", True): package_args.append("--compile-app") if self.get_bool_setting( self.options.compile_packages, "compile.packages", True ): package_args.append("--compile-packages")get_bool_setting的第三个参数default_value传入了True,即当命令行与pyproject.toml均未配置时,编译默认开启,随后把--compile-app/--compile-packages透传给打包工具链执行编译。
为什么默认开启:移动端冷启动的 zipimport 困境
编译收益的核心在于:预先编译好字节码,省去每次启动时的重复编译。
在桌面端(Windows / macOS / Linux)和 Web 端,Python 通常从真实文件系统加载模块,__pycache__可以把编译结果写回磁盘缓存,因此「首次编译、之后复用」的模式代价较低。但移动端(尤其是 Android)的打包方式完全不同:
- 纯 Python 代码被打包进存储型(stored,非压缩)zip,运行时通过
zipimport从 zip 中导入; - 由于 zip 是只读存储、且字节码缓存无法写回 zip 内部,
__pycache__缓存机制完全失效; - 结果是:每次冷启动时,每一个被导入的模块都必须从源码重新编译。
在中等配置的 Android 设备上,这一重复编译大约会带来1~2 秒的启动耗时,且该开销随导入模块数量线性增长——应用依赖越多、启动越慢。
这一背景在源码注释中亦有体现:build_base.py 明确写道,Android 采用 serious_python 的 native-mmap 打包方式,纯 Python 代码以 stored zip 形式存在并通过zipimport读取。既然缓存无法落盘,唯一的根治方案就是把「编译」从运行时前移到打包时——这正是本次默认值翻转的动机。
同时,编译开关本身并不是新功能,它一直存在,只是默认关闭。随着 Android 端转向 in-placezipimport打包,继续默认关闭意味着每次启动都要付出重复的冷启动代价,因此团队选择把默认值翻转为开启。
编译机制:compileall -b与 Web/Pyodide 兼容性验证
编译动作实际执行的是标准库命令python -m compileall -b。其中-b(--inplace的简写)表示就地写入.pyc,即把编译产物写到与源文件相同的位置,而不是标准的__pycache__子目录,这样在剥离.py源文件后,字节码文件能保持原有的模块路径结构,导入不受影响。
关于 Web 构建,有一个值得单独说明的兼容性验证点:Flet 的 Web 构建依赖 Pyodide(在浏览器中运行的 CPython)。打包时,用于编译的独立 CPython 与运行时的 Pyodide 需要共享相同的次要版本号(例如都是 3.14),并且 CPython 保证.pyc的 magic number 在某一次要版本的所有补丁版本间保持稳定——因此由宿主 CPython 预编译的字节码,能被 Pyodide 运行时正确接受。换言之,只要编译环境与 Pyodide 的 Python 次版本一致,预编译字节码在浏览器中加载就没有兼容性问题。
迁移指南:大多数应用无需任何改动
对绝大多数应用来说,这次变更完全透明:构建产物启动更快,代码无需修改。升级到 0.86.0 后直接重新构建即可。
以下情况属于需要关注的特例,如果符合其中之一,建议显式关闭编译:
- 依赖
.py源码调试:你希望在 bundle 中保留可读的 Python 源码,例如用于线上排查、traceback 中查看源码行(未编译时 traceback 能显示源码行,编译后只剩字节码); - 允许带语法错误构建:源码存在语法错误时,
compileall会失败导致构建无法完成;不编译则可以跳过此环节继续打包; - 希望加速迭代构建:编译环节本身耗时,对于频繁的临时构建(如本地联调),跳过编译能缩短构建时间。
通过 CLI 关闭编译
0.86.0 中,--compile-app与--compile-packages已改为argparse.BooleanOptionalAction类型,因此每个参数都自动获得了--no-前缀形式;原有的--compile-app/--compile-packages写法依旧有效。源码中可见参数定义(build_base.py):
parser.add_argument( "--compile-app", dest="compile_app", action=argparse.BooleanOptionalAction, default=None, help="Pre-compile app's `.py` files to `.pyc` (on by default; " "use --no-compile-app to disable)", )对应的关闭命令示例:
flet build apk --no-compile-app --no-compile-packages--no-compile-app只关闭应用自身源码的编译,--no-compile-packages只关闭 site-packages 中已安装包的编译,两者可独立使用,按需组合。
通过pyproject.toml关闭编译
在项目根目录的pyproject.toml中配置:
[tool.flet.compile] app = false packages = false这样即使用户在命令行忘记加--no-参数,项目配置也会统一强制关闭编译。
除了全局配置,Flet 还支持按平台覆盖,例如只对 Android 构建关闭:
[tool.flet.android.compile] app = false packages = false平台名与构建目标对应:apk/aab对应android,ipa对应ios,另有web、windows、macos、linux等(完整平台映射见 build_base.py)。
参数解析优先级
编译开关的最终取值遵循如下解析顺序(优先级从高到低):
- CLI 命令行参数(
--compile-app/--no-compile-app等); [tool.flet.<platform>.compile](平台级配置,如[tool.flet.android.compile]);[tool.flet.compile](全局配置);- 默认值
true(即默认开启编译)。
这一优先级顺序在源码中由get_bool_setting精确实现(build_base.py):先检查 CLI 选项是否为None,非空则直接采用;否则依次回退到tool.flet.<platform>.<setting>、tool.flet.<setting>,最后落到默认值。官方发布文档 Compilation and cleanup 中的「Resolution order」一节给出了同样的优先级说明,可作为对照参考。
配套机制与注意事项
编译与 Cleanup 的关系
与编译配套的还有清理(cleanup)机制,二者常被一起讨论。flet build支持四类清理开关:
cleanup-app:清理应用目录中的垃圾文件(默认关闭);cleanup-app-files:指定额外 glob 删除应用目录文件(指定后隐式启用cleanup-app);cleanup-packages:清理 site-packages 中的垃圾文件(默认开启);cleanup-package-files:指定额外 glob 删除 site-packages 文件(指定后隐式启用cleanup-packages)。
命令行组合示例(来源:publish/index.md):
flet build <target_platform> \ --compile-app --compile-packages \ --cleanup-app-files "**/*.c" "**/*.h" --cleanup-package-files "**/*.pyi"对应的pyproject.toml写法:
[tool.flet.compile] # 或 [tool.flet.<PLATFORM>.compile] app = true packages = true [tool.flet.cleanup] # 或 [tool.flet.<PLATFORM>.cleanup] app = true packages = true app_files = ["**/*.c", "**/*.h"] package_files = ["**/*.pyi"]需要注意的是,cleanup系列与compile系列共用同一套「CLI → 平台级 → 全局 → 默认值」的优先级解析逻辑(由同一个get_bool_setting支撑),只是默认值不同:cleanup-app默认为false/空列表,cleanup-packages默认为true。
macOS 签名陷阱:关闭编译的副作用
一个与关闭编译直接相关的实战提示来自 macOS 发布文档:如果在 macOS 构建中关闭编译(--no-compile-app/--no-compile-packages),运行时 Python 会尝试在 bundle 内部创建__pycache__缓存目录,这会在签名之后修改 bundle 内容,进而触发"MyApp" is damaged and can't be opened之类的签名校验失败(详见 macos.md)。因此,macOS 发布构建建议保持默认的编译开启状态。
Python 版本切换会失效字节码
另一个源码层面的防御性设计值得了解:.pyc字节码与编译它的 Python 版本强绑定(magic number 不匹配时运行时会报bad magic number)。为此,构建目录中会写入一个.python-version标记文件,当检测到打包的 Python 版本发生变化时,flet-cli 会自动清空构建目录强制全量重建,避免把旧版本字节码混入新版本 bundle(见 build_base.py)。所以,在使用--python-version或修改项目requires-python切换 Python 版本时,无需手动清理build/目录。
时间线与参考
- 变更版本:
0.86.0 - 关联文档:编译与清理的完整 API 说明见 Compilation and cleanup;构建命令参数参考见 flet-build CLI 文档;版本发布说明见 release notes
- 相关 PR:
#6598(该变更对应的实现与讨论)
- 前端
- 跨平台
- 桌面应用
- 移动开发
【免费下载链接】flet
Build realtime web, mobile and desktop apps in Python only. No frontend experience required.
相关推荐
Flet 应用分发打包指南:从 `flet build` 看懂全平台构建机制
Flet 应用分发打包指南:从 flet build 看懂全平台构建机制 flet build 是 Flet CLI 提供的一键打包命令,它能将 Python
前端跨平台桌面应用移动开发Flet `flet pack` 命令完全指南:将 Python 编写的 Flet 应用打包为独立桌面程序
Flet flet pack 命令完全指南:将 Python 编写的 Flet 应用打包为独立桌面程序 flet pack 是 Flet 官方 CLI( fle
前端跨平台桌面应用移动开发Flet `flet publish` 命令详解:零 Flutter 依赖,将 Python 应用发布为 Pyodide 静态网站
Flet flet publish 命令详解:零 Flutter 依赖,将 Python 应用发布为 Pyodide 静态网站 flet publish 是 F
前端跨平台桌面应用移动开发
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考