- 开发工具
- CLI
【免费下载链接】PyOxidizer
A modern Python application packaging and distribution tool
本文围绕 PyOxidizer 的 Starlark 构建配置展开,系统讲解每个资源对象上以add_前缀暴露的七个属性——add_include、add_location、add_location_fallback、add_source、add_bytecode_optimization_level_zero/one/two——它们共同决定了一个 Python 资源(模块源码、字节码、包资源、扩展模块等)在加入资源收集器(resource collector)时是否被收录、放在哪里、以何种形态(源码 / 各级优化字节码)进入最终产物。读完本文,你将能够精确控制 PyOxidizer 产物的资源构成,实现"只打包所需、按需选位置、隐藏源码、裁剪字节码"等细粒度打包策略。
在 PyOxidizer 中,所有参与打包的 Python 资源(PythonModuleSource、PythonPackageResource、PythonExtensionModule等)在加入资源收集器时,都会携带一组以add_前缀命名的属性。这些属性并不是凭空产生的:它们由创建该资源时所关联的PythonPackagingPolicy派生而来(参见 PythonPackagingPolicy 类型文档),但 Starlark 配置代码可以在资源真正被加入集合之前,逐个修改这些属性,从而对单个资源实行"特判"。理解这七个属性,是精确控制 PyOxidizer 打包结果的第一步。
一、机制总览:属性从哪里来,到哪里去
从实现上看,"add 上下文"对应 Rust 结构体PythonResourceAddCollectionContext,它承载了全部七个字段:include、location、location_fallback、store_source、optimize_level_zero、optimize_level_one、optimize_level_two(见 python-packaging/src/resource_collection.rs)。
资源创建时的默认值并非硬编码在资源对象上,而是由PythonPackagingPolicy.derive_add_collection_context()依据当前策略实时计算出来的(见 python-packaging/src/policy.rs):
include由策略的include_classified_resources/include_file_resources/include_test/include_distribution_sources/include_distribution_resources等开关经filter_python_resource()综合推导(见 python-packaging/src/policy.rs);location与location_fallback直接取自策略的resources_location与resources_location_fallback;store_source按模块是否属于标准库,取自include_distribution_sources或include_non_distribution_sources;- 三个
optimize_level_*字段取自策略同名属性,但对注册过register_no_bytecode_module()的模块会强制置为False。
在 Starlark 层,这些属性通过ResourceCollectionContexttrait 暴露为可读写的对象属性,add_collection_context_attrs()明确列出了这七个属性名(见 pyoxidizer/src/starlark/python_resource.rs),get/set 分别由get_attr_add_collection_context()与set_attr_add_collection_context()实现(见 pyoxidizer/src/starlark/python_resource.rs)。
因此,资源的默认行为完全由策略决定;而 Starlark 代码对add_*属性的赋值,等价于对这份上下文做就地覆盖。下面逐一说明七个属性的语义。
二、add_include:是否真正加入集合
add_include是bool类型,定义了一个"是与否"的过滤器,决定该资源是否真的被加入集合。
如果某个资源以.add_include = False的状态被提交给集合,那么这次添加会被当作**空操作(no-op)**处理,集合不会发生任何改变。
底层行为与PythonResourceCollector中各类add_*_with_context方法的第一道检查一一对应:add_python_module_source_with_context()、add_python_module_bytecode_with_context()、add_python_package_resource_with_context()等在入口处都会先判断!add_context.include,若为真则直接返回AddResourceAction::NoInclude(...)(见 python-packaging/src/resource_collection.rs),对应的动作描述为ignored adding <name> because fails inclusion filter。
默认值推导规则(从filter_python_resource()的实现归纳):
| 资源类型 | add_include默认取决于 | 默认值 |
|---|---|---|
分类资源(PythonModuleSource等) | include_classified_resources | True |
File资源 | include_file_resources | False |
| 标准库模块源码 | include_distribution_sources | True |
| 非标准库模块源码 | include_non_distribution_sources | True |
| 标准库包资源 | include_distribution_resources | False |
| 测试相关资源 | include_test | False |
注意PythonExtensionModule在filter_python_resource()中的默认推导结果是False——扩展模块的收录走的是更复杂的专用路径(见下文"位置约束"),不要误以为扩展模块默认不被打包。
策略层的include_*开关与新项目模板中的注释一一对应(见 pyoxidizer/src/templates/new-pyoxidizer.bzl.hbs),例如取消注释policy.include_distribution_sources = False即可让所有标准库模块的add_include默认变为False。
三、add_location:主存放与加载位置
add_location是string类型,定义该资源主要应被添加到的位置,以及运行时应当从哪里加载。可取值如下:
in-memory资源从内存加载。
- 对于 Python 模块与资源文件,自定义模块导入器(custom module importer)将以0-copy方式从内存加载模块;
- 对于 Python 扩展模块,扩展可以被静态链接进构建出的二进制,或者作为共享库从内存加载(后者并非所有平台都支持,Windows 是典型支持平台)。
filesystem-relative:<prefix>资源被物化(materialized)到文件系统上、相对于构建产物(built entity)的某个位置,并在运行时从文件系统加载。
<prefix>是放置资源的目录前缀;使用.(即filesystem-relative:.)表示与构建产物同一目录。
从 Starlark 层看,OptionalResourceLocation的字符串转换逻辑只接受三种形态——default、in-memory、filesystem-relative:*(见 pyoxidizer/src/starlark/python_resource.rs):
- 在读取时,
in-memory序列化为字符串in-memory,filesystem-relative:<prefix>序列化为filesystem-relative:<prefix>; - 在写入时,若给
add_location赋一个无法转换成具体位置的值(例如None/default),set_attr_add_collection_context()会抛出OperationNotSupported错误——因为add_location必须是一个具体位置(见 pyoxidizer/src/starlark/python_resource.rs)。
add_location的默认值来自策略的resources_location,其默认值为in-memory(见 python-packaging/src/policy.rs)。全局切换位置可直接修改策略:
policy = dist.make_python_packaging_policy() # 默认:全部资源从内存加载 # policy.resources_location = "in-memory" # 改为:资源物化到相对于二进制的 lib 目录 policy.resources_location = "filesystem-relative:lib" # 与构建产物同目录 # policy.resources_location = "filesystem-relative:."运行时加载路径与PythonExecutable.packed_resources_load_mode、module_search_paths等解释器配置相互配合,详见 PythonExecutable 类型文档 与 PythonEmbeddedResources 类型文档。
四、add_location_fallback:首选位置不满足时的备选
add_location_fallback是string或None类型的属性,语义与add_location相同,区别在于:仅当add_location指定的位置无法被满足时,它才会发挥作用。
这一点对 Python 扩展模块尤其重要。扩展模块并不能存在于所有位置:
- 内存加载共享库需要平台支持(Windows 支持、多数 Unix 平台不支持);
- 静态链接成 builtin 需要扩展提供目标文件/静态库数据;
- 文件系统加载需要扩展以共享库形式存在。
设置add_location_fallback为不同的位置,可以在打包具有位置约束的资源时获得更大的灵活性。从PythonResourceCollector的扩展模块专用入口add_python_extension_module_with_context()的实现可以推断,该方法会根据主位置与回退位置的组合,在"内置扩展(builtin)""独立共享库文件""从内存加载动态库"等几种产物形态之间做取舍(见 python-packaging/src/resource_collection.rs)。典型用法是"首选内存、失败则落到文件系统":
policy = dist.make_python_packaging_policy() # 资源优先尝试从内存加载;不满足时物化到相对二进制 lib 目录 policy.resources_location = "in-memory" policy.resources_location_fallback = "filesystem-relative:lib"与add_location不同,add_location_fallback可以被赋值为None以清除回退位置(见 pyoxidizer/src/starlark/python_resource.rs)。上述两个策略属性对应新项目模板中的示例(见 pyoxidizer/src/templates/new-pyoxidizer.bzl.hbs)。
五、add_source:是否收录 Python 源码
add_source是bool类型,定义是否为一个 Python 模块添加源代码。
对于 Python 模块,运行时通常只需要字节码。但对某些应用而言:
- 源码的存在价值不高(运行时用不到);
- 开发者不希望分发源码(例如出于代码混淆/保密目的)。
把add_source设为False可以阻止该模块的源码被加入集合。
从derive_add_collection_context()的实现可见,store_source的默认值按模块归属区分:标准库模块取include_distribution_sources,非标准库模块取include_non_distribution_sources(见 python-packaging/src/policy.rs)。
从add_python_module_source_with_context()的实现可以确认它的实际效果:store_source为真时才会把源码写入集合(写入in_memory_source或relative_path_module_source),为假时跳过源码、只依据optimize_level_*生成对应字节码请求(见 python-packaging/src/resource_collection.rs)。
六、三个字节码优化等级属性:add_bytecode_optimization_level_*
Python 解释器支持多个字节码优化等级(0/1/2),其中等级 0 是默认优化等级。三个bool属性分别控制是否添加对应等级的字节码:
add_bytecode_optimization_level_zero:是否添加优化等级 0 的字节码。为True时,Python 源码会在构建期被编译成字节码;add_bytecode_optimization_level_one:是否添加优化等级 1 的字节码;add_bytecode_optimization_level_two:是否添加优化等级 2 的字节码。
每个属性的默认值就是策略上同名属性PythonPackagingPolicy.bytecode_optimize_level_zero / _one / _two的当前值(见 pyoxidizer/docs/pyoxidizer_config_type_python_packaging_policy.rst)。策略的默认配置为:等级 0 =True,等级 1 =False,等级 2 =False(见 python-packaging/src/policy.rs),即默认只生成优化等级 0 的字节码。
字节码的生成发生在构建期:PythonResourceCollector在收尾阶段会借助PythonBytecodeCompiler把源码编译成各等级字节码并写入in_memory_bytecode*/relative_path_bytecode*字段(见 python-packaging/src/resource_collection.rs)。如果提交的是显式字节码资源而对应的优化等级开关为False,则返回AddResourceAction::BytecodeOptimizationLevelMismatch,即"因优化等级不匹配而忽略该字节码"(见 python-packaging/src/resource_collection.rs)。
典型裁剪用法:
policy = dist.make_python_packaging_policy() # 只生成优化等级 0 的字节码(默认即如此) # policy.bytecode_optimize_level_zero = True # policy.bytecode_optimize_level_one = False # policy.bytecode_optimize_level_two = False对应新项目模板中的注释示例(见 pyoxidizer/src/templates/new-pyoxidizer.bzl.hbs)。
七、实战:在 Starlark 中修改add_属性
有两种典型途径修改add_属性。
7.1 对已生成的资源逐个改写
从pip_install()、read_virtualenv()、read_package_root()等方法拿到的资源列表,可以在加入集合前被遍历修改:
def make_exe(): dist = default_python_distribution() policy = dist.make_python_packaging_policy() exe = dist.to_python_executable( name = "myapp", packaging_policy = policy, ) resources = exe.pip_install(["requests"]) for r in resources: # 非标准库模块:丢弃源码,只保留字节码,降低产物体积并隐藏源码 if type(r) == "PythonModuleSource": r.add_source = False # 把第三方包资源从内存移到相对二进制的 lib 目录 if type(r) == "PythonPackageResource": r.add_location = "filesystem-relative:lib" exe.add_python_resources(resources)7.2 用register_resource_callback全局统一切入
PythonPackagingPolicy.register_resource_callback()注册一个 Starlark 函数,每当资源对象被创建时都会以(policy, resource)两个参数调用它(见 pyoxidizer/docs/pyoxidizer_config_type_python_packaging_policy.rst),从而在资源加入集合前统一改写add_属性。仓库自带的单元测试给出了可运行示例(见 pyoxidizer/src/starlark/python_packaging_policy.rs):
def make_exe(): dist = default_python_distribution() policy = dist.make_python_packaging_policy() # 全局回调:把 _ssl 扩展模块排除出产物 def cb(policy, resource): if type(resource) == "PythonExtensionModule": if resource.name == "_ssl": resource.add_include = False policy.register_resource_callback(cb) exe = dist.to_python_executable( name = "myapp", packaging_policy = policy, ) return exe对应测试test_stdlib_extension_module_disable验证了_ssl通过回调被排除的效果;反向测试test_stdlib_extension_module_enable则演示了在extension_module_filter = "minimal"的前提下,用resource.add_include = True把_ssl单独拉回集合(见 pyoxidizer/src/starlark/python_packaging_policy.rs)。这种"策略收紧 + 回调特判"的组合,是 PyOxidizer 最灵活的资源裁剪手段。
八、位置约束与平台注意点
add_location = "in-memory"对扩展模块而言,最终形态可能是"静态链接进二进制"或"从内存加载共享库"二者之一;从内存加载共享库并非所有平台都支持,策略上有专门的allow_in_memory_shared_library_loading开关(Windows 支持,其他平台会被忽略),详见 PythonPackagingPolicy 文档。- 当扩展模块既无法静态链接、又无法从内存加载共享库时,就必须依靠
filesystem-relative:*位置,add_location_fallback正是为此设计的兜底。 - 给
add_location赋None或default会触发OperationNotSupported错误(add_location必须是具体位置);而add_location_fallback允许赋None表示"无回退"。 - 丢弃源码(
add_source = False)后,模块运行时依赖的只能是字节码,请确保至少一个add_bytecode_optimization_level_*为True,否则该模块既无源码也无字节码,导入时会失败。
九、小结:七个属性一句话速查
| 属性 | 类型 | 作用 | 默认来源 |
|---|---|---|---|
add_include | bool | 是否真正加入集合(False时为 no-op) | include_classified_resources/include_file_resources等策略开关 |
add_location | string | 主存放/加载位置(in-memory或filesystem-relative:<prefix>) | resources_location(默认in-memory) |
add_location_fallback | string/None | 主位置不满足时的回退位置 | resources_location_fallback(默认None) |
add_source | bool | 是否收录 Python 模块源码 | include_distribution_sources/include_non_distribution_sources |
add_bytecode_optimization_level_zero | bool | 是否添加优化等级 0 字节码 | bytecode_optimize_level_zero(默认True) |
add_bytecode_optimization_level_one | bool | 是否添加优化等级 1 字节码 | bytecode_optimize_level_one(默认False) |
add_bytecode_optimization_level_two | bool | 是否添加优化等级 2 字节码 | bytecode_optimize_level_two(默认False) |
掌握这七个属性,你就掌握了 PyOxidizer 资源收集阶段的"最后一道阀门":配合 PythonPackagingPolicy 策略文档、资源打包总览 与 扩展模块打包指南,可以按模块粒度精确编排产物的资源形态、存放位置与源码可见性,为体积裁剪、源码保护和平台兼容性设计提供完整控制力。
- 开发工具
- CLI
【免费下载链接】PyOxidizer
A modern Python application packaging and distribution tool
相关推荐
Ghost Downloader 3 快速上手:10 分钟跑通这款多协议下载工具
Ghost Downloader 3 快速上手:10 分钟跑通这款多协议下载工具 Ghost Downloader 3 是一款用 Python + PySide
开发工具CLIPyOxidizer 深度解析:PythonModuleSource 源码模块资源的类型、属性与打包控制
PyOxidizer 深度解析:PythonModuleSource 源码模块资源的类型、属性与打包控制 PythonModuleSource 是 PyOxid
开发工具CLISmartCodable容错处理机制:如何优雅应对后端数据异常
SmartCodable容错处理机制:如何优雅应对后端数据异常 在移动应用开发中,后端接口返回的数据往往难以完全符合预期格式。当遇到字段缺失、类型不匹配或格式错
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考