三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

PyInstaller打包的Matplotlib程序从40MB瘦身到17MB:我的实战记录与思考

PyInstaller打包的Matplotlib程序从40MB瘦身到17MB:我的实战记录与思考

PyInstaller打包的Matplotlib程序从40MB瘦身到17MB:我的实战记录与思考

最近在开发一个基于Matplotlib的数据可视化工具时,遇到了一个让所有Python开发者都头疼的问题——打包后的程序体积过大。一个简单的散点图生成工具,经过PyInstaller打包后竟然达到了40MB!这对于需要分发给用户的程序来说,显然不够理想。于是,我决定深入研究如何为Python程序"瘦身",经过一番折腾,最终成功将程序体积缩减到了17MB。下面是我的完整实战记录和一些思考。

1. 为什么Python程序打包后体积这么大?

在开始瘦身之前,我们需要先理解为什么Python程序打包后会变得如此臃肿。这主要与Python的运行机制和生态系统有关:

  1. 解释器本身:PyInstaller打包时会包含一个精简版的Python解释器,这部分大约占15-20MB。
  2. 依赖库的完整打包:即使你只用了某个库的很小一部分功能,PyInstaller也会打包整个库。
  3. 资源文件:像Matplotlib这样的库会附带大量字体文件、图标等资源。
  4. 动态链接库:许多科学计算库依赖的底层C/C++库也会被包含进来。

以Matplotlib为例,它包含了:

  • 20多种字体文件(每种约200-500KB)
  • 多个后端实现(Qt、Tk、GTK等)
  • 示例数据和图像
# 查看Matplotlib安装目录下的文件结构 import matplotlib print(matplotlib.__path__)

2. 构建最小化打包环境

要实现有效瘦身,首先需要创建一个干净的虚拟环境,只安装必要的依赖:

# 创建虚拟环境 python -m venv minimal_env source minimal_env/bin/activate # Linux/macOS minimal_env\Scripts\activate # Windows # 仅安装必要包 pip install numpy matplotlib pyinstaller

关键点

  • 不要使用全局Python环境,避免打包不必要的依赖
  • 精确指定依赖版本,避免安装额外依赖项
  • 使用pip list检查安装的包是否确实必要

3. PyInstaller基础打包与体积分析

我们先进行基础打包,了解初始体积情况:

pyinstaller --onefile --clean scatter_plot.py

打包完成后,分析生成的文件结构:

dist/ └── scatter_plot.exe # 40.1MB build/ └── scatter_plot/ └── _internal/ # 35.0MB ├── numpy/ # 12.3MB ├── matplotlib/ # 18.7MB ├── PyQt5/ # 3.2MB └── ...

通过分析发现,Matplotlib占据了近一半的体积,其中:

  • matplotlib/mpl-data目录:9.2MB(主要是字体和样式文件)
  • matplotlib/backends目录:4.1MB(多种GUI后端)
  • matplotlib/ft2font等二进制扩展:3.4MB

4. 针对性瘦身策略与实施

4.1 移除不必要的Matplotlib资源

Matplotlib默认包含了许多我们可能用不到的字体和样式文件。我们可以通过以下方式精简:

# 在代码中指定只使用特定字体 import matplotlib as mpl mpl.rcParams['font.sans-serif'] = ['Arial'] # 只保留Arial字体

然后手动删除mpl-data中不必要的文件:

  1. 定位Matplotlib数据目录:
    import matplotlib print(matplotlib.get_data_path())
  2. 仅保留:
    • fonts/ttf/Arial.ttf
    • stylelib/_classic_test.mplstyle
  3. 删除其他字体和样式文件

效果:减少约8MB空间

4.2 选择最小化后端

Matplotlib支持多种GUI后端,但我们的控制台程序可能只需要最基本的:

import matplotlib matplotlib.use('Agg') # 使用非交互式后端

然后在PyInstaller spec文件中排除不必要的后端:

# 修改spec文件 a = Analysis( ... excludes=['PyQt5', 'PySide2', 'wx', 'gtk'], )

效果:减少约3MB空间

4.3 使用UPX压缩二进制文件

UPX是一款优秀的可执行文件压缩工具:

# 安装UPX # 然后使用PyInstaller时添加UPX选项 pyinstaller --onefile --upx-dir=/path/to/upx scatter_plot.py

注意

  • UPX可能会增加程序启动时间
  • 某些杀毒软件可能会误报

效果:减少约15%体积

4.4 手动清理PyInstaller打包结果

打包完成后,我们可以手动检查并删除不必要的文件:

  1. 删除_internal目录中未使用的Python标准库
  2. 移除测试和文档文件
  3. 清理.pyc.pyo缓存文件
# 示例清理命令(Linux/macOS) find dist -name "*.pyc" -delete find dist -name "*.pyo" -delete find dist -name "__pycache__" -exec rm -rf {} +

5. 自动化瘦身工具开发

为了简化这个过程,我开发了一个简单的Python脚本来自动化瘦身流程:

import os import shutil import json from pathlib import Path def clean_pyinstaller_build(build_dir): # 加载白名单 with open('white_files.json') as f: white_list = json.load(f) # 遍历目录 for root, dirs, files in os.walk(build_dir): for file in files: file_path = Path(root) / file rel_path = file_path.relative_to(build_dir) # 检查是否在白名单中 if str(rel_path) not in white_list: # 移动文件到备份目录 backup_dir = Path(f"{build_dir}_new") backup_path = backup_dir / rel_path backup_path.parent.mkdir(parents=True, exist_ok=True) shutil.move(file_path, backup_path) print(f"清理完成,移除了 {len(os.listdir(backup_dir))} 个文件")

白名单示例(white_files.json):

{ "numpy/core/_multiarray_umath.pyd": true, "matplotlib/backends/_backend_agg.pyd": true, "matplotlib/ft2font.cp39-win_amd64.pyd": true, "PIL/_imaging.cp39-win_amd64.pyd": true }

6. 瘦身效果验证与程序稳定性测试

完成瘦身后,必须进行全面的功能测试:

  1. 基本功能测试:确保所有核心功能正常工作
  2. 边界测试:测试各种输入条件下的程序行为
  3. 性能测试:检查启动时间和内存使用情况
  4. 兼容性测试:在不同Windows版本上运行

测试脚本示例:

import subprocess import time def test_program(): start_time = time.time() result = subprocess.run(['dist/scatter_plot.exe'], capture_output=True, text=True) elapsed = time.time() - start_time print(f"启动时间: {elapsed:.2f}秒") print(f"返回值: {result.returncode}") print(f"输出: {result.stdout[:100]}...") assert result.returncode == 0 assert os.path.exists('scatter_plot.png')

7. 深入思考:瘦身的代价与收益

经过这次瘦身实践,我总结了一些值得思考的问题:

优点

  • 减少分发体积,提高下载和传输效率
  • 可能提高程序启动速度(减少了文件扫描时间)
  • 更清晰的依赖管理

潜在问题

  • 增加了开发和维护成本
  • 可能引入兼容性问题
  • 后续更新可能需要重新评估文件依赖

经验法则

  1. 对于一次性脚本,可能不需要过度优化
  2. 对于分发给终端用户的应用程序,瘦身很有价值
  3. 在开发早期就考虑依赖管理,避免后期优化困难

在实际项目中,我发现最有效的策略组合是:

  1. 使用干净的虚拟环境
  2. 明确指定Matplotlib配置
  3. 应用UPX压缩
  4. 手动移除不必要的资源文件

经过这些优化,我的Matplotlib程序从最初的40MB成功缩减到了17MB,而且保持了所有核心功能。这个过程中最重要的是理解每个文件的作用,而不是盲目删除。

← 返回列表