当前位置: 首页 > news >正文

【Python】.pyz:源码与依赖打包

.pyz 是 Python 的可执行压缩包格式,本质是一个 ZIP 文件,包含 Python 源码和依赖模块,解释器可直接运行,无需解压或安装。从 Python 3.5+ 开始,官方通过 zipapp 模块支持创建和运行 .pyz 文件。本教程介绍如何使用脚本自动打包项目源码和依赖为仅含 .pyc.pyz 文件,优化分发和执行效率。

打包原理

.pyz 文件是一个 ZIP 压缩包,包含以下内容:

  • 项目文件:源码(.py.pyc.pyd)及依赖模块。
  • 入口文件:通常为 __main__.py,Python 运行 .pyz 时默认执行其中的 main 函数。
  • 运行机制:Python 解释器自动解压 .pyz 并执行入口函数,支持跨平台分发。

创建简单的 .pyz 包

以下通过示例项目说明如何打包为 .pyz 文件,支持自动检测 __main__.py 或手动指定入口。

示例项目结构

myproject/
├── main.py
└── module/└── utils.py

目标:将 myproject 打包为 myproject.pyz,自动使用 __main__.py 或手动指定入口。

准备入口文件

Python 默认运行 .pyz 中的 __main__.py。你可以:

  1. 重命名或创建 __main__.py:将 main.py 重命名为 __main__.py,或新建 __main__.py,示例内容:

    # myproject/__main__.py
    from module import utilsdef main():print("Hello from pyz!")utils.do_something()if __name__ == '__main__':main()
    
  2. 直接使用现有 __main__.py:如果源码目录已包含 __main__.py,无需额外定义 main 函数或指定入口。

使用 zipapp 打包

myproject 的上级目录运行:

python -m zipapp myproject -o myproject.pyz
  • 自动入口:如果 myproject 包含 __main__.pyzipapp 默认执行其 __main__ 块,无需 -m 参数。

  • 手动指定入口:若无 __main__.py 或需指定其他入口,使用 -m 参数,例如:

    python -m zipapp myproject -o myproject.pyz -m "main:main"
    
  • 参数说明:

    • myproject:源码目录。
    • -o myproject.pyz:输出文件名。
    • -m "module:function":手动指定入口模块和函数(如 main.pymain 函数)。
  • 结果:生成 myproject.pyz,可通过 python myproject.pyz 运行。

高级打包脚本

手动打包适用于简单项目,但复杂项目需包含依赖(如 site-packages)并优化为 .pyc 文件以减小体积和保护源码。以下脚本自动完成这些任务。

脚本功能

  • 输入
    • --site-packages:依赖目录,默认自动检测虚拟环境或系统的 site-packages
    • --src:源码目录,默认当前目录。
    • --out:输出 .pyz 文件名,默认使用源码目录名加 .pyz
    • --entry:入口函数,格式 module:function,默认 main:main
  • 流程
    1. 复制 site-packages 和源码(.py.pyc.pyd)到临时目录。
    2. 排除无关文件(如 _virtualenv.py_virtualenv.pth)和目录(如 __pycache__)。
    3. 编译 .py.pyc,删除 .py 文件。
    4. 打包为压缩 .pyz 文件。

脚本代码

import os
import sys
import shutil
import zipapp
import argparse
import compileall
import tempfile
import site# 获取 site-packages 路径,优先选择标准路径或当前目录
candidates = [p for p in site.getsitepackages() if p.endswith("site-packages")] if hasattr(site, "getsitepackages") else []
default_site_packages = candidates[0] if candidates else next((p for p in sys.path if "site-packages" in p), os.path.abspath("."))
# 如果路径非 site-packages,尝试拼接虚拟环境标准位置
if os.path.basename(default_site_packages) != "site-packages":candidate = os.path.join(default_site_packages, "Lib", "site-packages")if os.path.exists(candidate):default_site_packages = candidate# 设置命令行参数解析
parser = argparse.ArgumentParser(description="打包 site-packages 和源码为仅含 pyc 的压缩 pyz,排除无用文件夹")
parser.add_argument("--site-packages", default=default_site_packages, help="虚拟环境或系统的 site-packages,默认当前解释器对应路径")
parser.add_argument("--src", default=".", help="源码目录,默认当前目录")
parser.add_argument("--out", default=None, help="输出 pyz 文件名,默认源码目录名加 .pyz")
parser.add_argument("--entry", default="main:main", help="入口函数,格式 module:function,默认 main:main")
args = parser.parse_args()# 设置默认输出文件名基于源码目录
if args.out is None:args.out = os.path.basename(os.path.abspath(args.src)) + ".pyz"# 检查 site-packages 目录是否存在
site_packages_dir = args.site_packages
if not os.path.exists(site_packages_dir):print(f"错误: 找不到 site-packages 目录:{site_packages_dir}", file=sys.stderr)sys.exit(1)
print(f"指定的 site-packages 目录:{site_packages_dir}")# 定义排除的文件和目录
exclude_files = {"_virtualenv.py", "_virtualenv.pth"}
exclude_dirs = {"__pycache__"}# 创建临时目录用于构建
with tempfile.TemporaryDirectory() as build_dir:site_dir = os.path.join(build_dir, "site")os.makedirs(site_dir, exist_ok=True)# 复制 site-packages 中的文件和目录for item in os.listdir(site_packages_dir):if item in exclude_files:print(f"跳过文件:{item}")continues = os.path.join(site_packages_dir, item)d = os.path.join(site_dir, item)if os.path.isdir(s):if item in exclude_dirs:print(f"跳过目录:{item}")continueshutil.copytree(s, d)print(f"复制目录:{s} -> {d}")else:shutil.copy2(s, d)print(f"复制文件:{s} -> {d}")# 复制源码 .py、.pyc 和 .pyd 文件for f in os.listdir(args.src):if f.endswith((".py", ".pyc", ".pyd")) and f != os.path.basename(__file__):shutil.copy2(os.path.join(args.src, f), os.path.join(site_dir, f))print(f"复制源码文件:{f}")# 编译 .py 文件为 .pycprint("编译 .py 文件生成 .pyc ...")compileall.compile_dir(site_dir, force=True, legacy=True, quiet=1)# 删除源码 .py 文件for root, _, files in os.walk(site_dir):for file in files:if file.endswith(".py"):os.remove(os.path.join(root, file))print(f"删除源码文件:{os.path.join(root, file)}")# 打包为压缩 pyz 文件print(f"打包为压缩 pyz 文件:{args.out},入口函数:{args.entry}")zipapp.create_archive(site_dir, args.out, main=args.entry, interpreter=sys.executable, compressed=True)print(f"✅ 打包完成:{args.out}")

使用示例

假设项目结构如下:

myapp/
├── __main__.py
├── utils.py
└── lib/└── helper.py

运行脚本:

python pack.py --src myapp --site-packages /path/to/venv/lib/python3.8/site-packages
  • 结果:生成 myapp.pyz,包含 site-packages 和源码的 .pyc 文件,入口为 main:main(需确保 __main__.py 中有 main 函数)。
  • 运行:python myapp.pyz

总结

本教程展示了如何通过 zipapp 模块和自定义脚本将 Python 项目打包为 .pyz 文件。简单项目可直接使用 python -m zipapp,而复杂项目可利用提供的脚本,自动化处理依赖、编译和打包,生成高效、可分发的 .pyz 文件。

相关文章:

  • IPv6 | 地址解析 / 地址管理 / 邻居发现协议(NDP)/ 无状态自动配置(SLAAC)
  • Spring Boot自动配置原理
  • Spring Boot + MyBatis + Vue:全栈开发中的最佳实践
  • ASP3605芯片在煤炭设备电源管理中的可靠性设计与应用探索
  • mapbox进阶,mapbox-gl-draw绘图插件扩展,编辑支持右键取消节点
  • SAP调用api
  • 创客匠人服务体系解析:知识 IP 变现的全链路赋能模型
  • 【网工】华为配置专题进阶篇③
  • FPGA--hello
  • MySQL慢SQL优化全攻略:从诊断到调优
  • C++ 学习笔记精要(二)
  • Android Kotlin 用法对比Java使用小结
  • Dilworth 定理 学习笔记
  • 智能危险品搬运机器人市场报告:行业趋势与未来展望
  • qt常用控件--01
  • 对于网站业务安全SCDN都能够从哪些方面进行保护?
  • Kafka协议开发总踩坑?3步拆解二进制协议核心
  • IP 风险画像网络违规行为识别
  • 语音相关-浏览器的自动播放策略研究和websocket研究
  • Kafka线上集群部署方案:从环境选型到资源规划思考
  • 云龙徐州网站开发/网站如何seo推广
  • 深圳外贸建站与推广/免费注册二级域名的网站
  • 小说网站编辑怎么做/免费推广的网站
  • 宣传中心网站建设/seo自动排名软件
  • 如何制作一个简单的网站/最近的大新闻
  • 使用推荐算法的网站开发 java/网站关键字优化软件