Python EXE 构建优化工具

自动检测可替换为最小存根的Python包,以减少可执行文件大小,支持PyInstaller、Nuitka和cx_Freeze构建工具。

  • by KoyoYeager
  • Python
  • 本地部署
  • 更新于
在 GitHub 查看 KoyoYeager/mcp-pystub

使用说明

mcp py存根

MCP服务器,在Python exe构建(PyInstaller/Nuitka/cx_Freeze)时自动检测存根可置换的包,生成最小存根代码。


一个MCP服务器,可以自动检测Python exe构建的可存根包(PyInstaller/Nuitka/cx_Freeze),并生成最小的存根代码以减少可执行文件的大小。

背景 / Background

当Python应用程序exe化时,依赖库在模块级import的重量包被整个捆绑在一起,exe尺寸膨胀。在实际的代码路径中不使用的包用最小虚拟(存根)置换的话可以大幅削减尺寸。

当将Python应用程序构建为可执行文件时,通过依赖关系在模块级别导入的重型包会被完全捆绑在一起,导致exe大小膨胀。用最少的存根替换未使用的包可以显著减小大小。

验证数据/验证结果

asammdf项目-已验证E2E行为:

|exe大小|动作|PySide6| |---|---|---|---| |无存根|431MB|确定|已加载| 应用asammdf.gui存根 259毫巴 好的 排除 | | 削减 | -40%(172毫巴) 没有影响

asammdf转换工具(分析结果):

stubbable:          pandas (59.7 MB), canmatrix (4.0 MB) — 合計 63.7 MB
submodule hints:    asammdf.gui → PySide6 (523 MB) 排除可能 — 50 hints 検出

手动存根化的3个软件包(pandas,canmatrix,asammdf.gui)全部自动检测

机能/ Features

工具/工具说明/说明 |---|---| | analyze 分析import图表,自动检测存根候补。C扩展包的间接排除提示也输出/Auto-detect stubbable packages + submodule stub hints for C-extension elimination | | graph 在节点边可视化/可视化import graph as nodes and edges | check 详细分析特定软件包的使用情况/Deep analysis of a specific package's usage | generate 生成软件包存根的最小代码+构建过程输出/Generate minimal stub code + build instructions | | generate_submodule | C生成间接删除扩展包的子模块存根 /生成子模块存根以间接消除C扩展包|

判定结果/ Verdicts

判定 / Verdict 意味 / Meaning
倔强的 可以存根。未在项目执行路径中使用/Safe to stub.Not used in project's runtime path`
不追踪 有保护。--nofollow-import-to で除外推奨 / Protected import. Use --nofollow-import-to
必需的 无法存根。实际使用的/Cannot stub.Actually used at runtime|

能做的事

  • 非固定库通用检测:仅通过AST结构判定。无硬代码
  • 函数级使用跟踪: mdf.get() 使用,使用 mdf.to_dataframe() 不使用→pandas是stubbable
  • 区分日历和引用: isinstance(x, pd.DataFrame) 是stub-safepd.DataFrame(data) 实际使用
  • 发现类继承: class User(BaseModel) →pydantic是required
  • 模块级调用检测:跟踪import时执行的代码
  • 自动检测try/except保护:受保护的import nofollow 判定
  • 存根代码自动生成:仅参考符号的最小存根+构建过程(备份恢复验证)
  • 自动检测C扩展:包含.pyd/.so的软件包被判定为不能直接存根化
  • C扩展间接排除 (v0.2新):如果未使用import C扩展包的子模块,则可以将该子模块存根化,从而消除C扩展。PySide6(523MB)→asammdf.gui存根 exe 40%削减证实
  • 复元安全设计 (v0.2新):备份+固定版本pip+验证命令的三重安全措施
  • PyInstaller挂钩信息:通知需要禁用的挂接文件

不能做的事/限制事项

  • C扩展包(.pyd/.so)如果直接短截线化,则缺少C扩展,因此required判定。不过 generate_submodule 的可间接排除(v0.2对应)
  • 动的import(importlib.import_module(変数))通过静态分析无法跟踪(通过warnings通知)
  • PyInstaller定制挂钩:可能会与短截线碰撞,需要手动禁用挂接
  • 延迟初始化模式: __init__ 在后面的方法中使用的软件包检测精度下降(在安全方面判定为required)
  • pip名称与import名称不匹配: python-dateutil → dateutil 等映射不对应
  • 运行时条件分支: if sys.version < (3,11) 中的import不能通过静态分析来判断

安全性の设计方针/ Safety Policy

“虽然判定为stubbable,但实际上是必要的”绝对不会引起的设计。如果在判定中犹豫不决,则将其推倒到安全侧。反向误判(实际上是stubbable,required)是允许的。

分析算法/Analysis Algorithm

  1. Import 抽出: ast.parse() 分析每个文件的import语句(区分模块级别/完整级别/try-except保护)
  2. 导入图表构建:通过BFS从入口点递归解析依赖关系(自动分类stdlib/third_party/local)
  3. 使用分析:gateway函数叫出来函数),并跟踪项目代码是否正在调用它。仅名称引用(isinstance,类型注释)作为stub-safe排除
  4. Module-level 検出:如果在import时调用包函数,则升级为required
  5. 子模块间接排除 (v0.2):确定正在import C扩展包的子模块,如果项目没有直接import+re-export符号,则输出存根提示

安装/安装

pip install mcp-pystub

依赖包/从属关系

  • mcp>=1.0.0 -模型上下文协议SDK
  • 分析引擎仅使用Python标准库(ast,importlib,pathlib)

使用方法

启动/运行为MCP服务器

mcp-pystub

克劳德桌面/克劳德代码設定

{
  "mcpServers": {
    "pystub": {
      "command": "mcp-pystub"
    }
  }
}

工具使用示例

分析

入力 / Input:
  entry_point: "C:/project/converter.py"
  python_path: "C:/project/.venv/Lib/site-packages"

出力 / Output:
  {
    "stubbable": [
      {"package_name": "pandas", "estimated_size_mb": 59.7, "reason": "依存ライブラリ経由でのみ import..."}
    ],
    "required": [
      {"package_name": "numpy", "reason": "プロジェクトコードが直接 import し使用"},
      {"package_name": "PySide6", "estimated_size_mb": 523.2,
       "reason": "C 拡張...ただし asammdf.gui をスタブ化することで間接排除が可能",
       "submodule_stubs": [{"submodule": "asammdf.gui", "target_package": "PySide6"}]}
    ],
    "nofollow": [
      {"package_name": "mpmath"}
    ],
    "submodule_stub_hints": [
      {"submodule": "asammdf.gui", "parent_package": "asammdf",
       "target_package": "PySide6", "imported_symbols": ["plot"]}
    ],
    "analysis_time_ms": 6478
  }

生成

入力 / Input:
  entry_point: "C:/project/converter.py"
  package_name: "pandas"

出力 / Output:
  {
    "files": {
      "pandas/__init__.py": "...",
      "pandas/core/api.py": "class DataFrame: pass\nclass Series: pass\n..."
    },
    "original_file_count": 2980,
    "stub_file_count": 266,
    "stub_total_bytes": 209530,
    "build_instructions": {
      "install_commands": ["pip uninstall -y pandas", "# cp stubs to site-packages"],
      "uninstall_commands": ["pip install pandas"],
      "hook_disable": ["# hook-pandas*.py → .disabled"]
    }
  }

发电机子模块 (v0.2新)

入力 / Input:
  entry_point: "C:/project/converter.py"
  parent_package: "asammdf"
  submodule: "asammdf.gui"

出力 / Output:
  {
    "parent_package": "asammdf",
    "submodule": "asammdf.gui",
    "files": {
      "asammdf/gui/__init__.py": "\"\"\"Auto-generated stub...\"\"\"\ndef plot(*args, **kwargs): ..."
    },
    "eliminated_packages": ["PySide6", "scipy", "lxml"],
    "original_size_bytes": 5907331,
    "stub_size_bytes": 144,
    "build_instructions": {
      "backup_commands": ["cp -r .../asammdf/gui .../asammdf/gui.bak"],
      "install_commands": ["rm -rf .../asammdf/gui", "cp -r _stubs/asammdf/gui/ .../asammdf/gui/"],
      "uninstall_commands": ["mv .../asammdf/gui.bak .../asammdf/gui"],
      "verify_commands": ["python -c \"import asammdf; print('OK')\""]
    }
  }

性能/性能

|项目规模|分析时间|节点数| |-----------------|---------|---------| | 軽量 (click) | 335ms | 73 | |中规模(flask)| 1,743ms | 230 | |重量级(pandas)| 4,820ms | 491 | |超重量级(sympy)| 6,919ms | 681 |

测试/测试

python -m pytest tests/ -v

测试结果

|测试种类|件数|结果| |-----------|------|------| |单元测试(9模块)|91|全部通过| PyPI库大规模测试(requests、flask、pandas等)|84|崩溃0| PyInstaller exe构建+动作测试|22| 全 PASS | <asammdf E2E(MDF制作→读取→重采样)<1> exe 40%削减+正常动作 | |还原测试(备份→还原→验证)|4|全部成功|

许可证/许可证

麻省理工学院