查看: 290|回复: 3

Python stubmaker 存根生成:语法参数、实战案例与常见错误排查

[复制链接]
发表于 昨天 10:00 | 显示全部楼层 |阅读模式
stubmaker 是 Python 官方配套的 .pyi 存根文件自动生成工具,它对纯 Python 源码和 C/C++/Rust 编写的编译扩展(.pyd / .so)都有效,能自动推导函数签名、类属性、泛型并生成符合 PEP484/561 标准的类型提示文件。与内置的 stubgen 相比,stubmaker 最大的优势是可以解析二进制扩展库。

安装方式:
  1. pip install stubmaker
  2. # 可选安装类型检查依赖
  3. pip install stubmaker mypy typing-extensions
复制代码
从源码安装最新开发版:
  1. git clone https://github.com/osandov/stubmaker.git
  2. cd stubmaker
  3. pip install .
复制代码
要求 Python ≥3.7,可选依赖 mypy、cython、setuptools。

命令行基础语法:
  1. stubmaker [全局参数] 子命令 [子命令参数] 目标路径
复制代码
两个核心子命令:generate(生成存根)和 check(校验存根)。

全局参数:-h/--help、-v/--verbose、-q/--quiet、--python-exec PATH(指定解释器)、--cache-dir DIR(缓存目录)。

generate 子命令主要参数:
  1. -o/--output DIR        指定输出目录,默认与源码同目录
  2. -r/--recursive         递归扫描文件夹
  3. --ignore-private      跳过下划线开头的私有方法/变量
  4. --include-docstrings  将源码 docstring 写入 .pyi
  5. --no-types-eval       关闭运行时类型推导(安全模式,仅 AST 静态解析)
  6. --module-filter REGEX 只生成匹配正则的模块
  7. --exclude REGEX       排除匹配正则的文件/模块
  8. --incremental         增量更新,不覆盖手动修改的 .pyi
  9. --force               强制覆盖旧存根
  10. --stub-extension SUFFIX 自定义存根后缀,默认 .pyi
  11. --add-type-imports   自动补充 typing 模块导入
  12. --skip-constants     不生成模块常量存根
  13. --export-all         自动生成 __all__ 列表
复制代码

check 子命令参数:--strict(严格校验)、--mypy-config FILE(指定 mypy 配置文件)。

Python 内部 API 调用方式:
  1. from stubmaker import StubGenerator, GeneratorConfig
  2. config = GeneratorConfig(
  3.     recursive=True,
  4.     ignore_private=True,
  5.     output_dir="./stubs",
  6.     incremental=True
  7. )
  8. gen = StubGenerator(config=config)
  9. gen.generate_file("mylib/main.py")      # 生成单文件
  10. # gen.generate_package("mylib/")        # 生成整个包
  11. # gen.check_stub("stubs/main.pyi")     # 校验存根
复制代码

以下为 8 个典型实战案例,覆盖常见场景。

案例1:为单个纯 Python 文件生成基础存根(忽略私有方法)
  1. stubmaker generate --ignore-private utils.py
复制代码

案例2:递归批量为整个项目包生成存根,输出到独立 stubs 文件夹,使用增量更新并导出所有公有对象。
命令行:
  1. stubmaker generate -r -o ./stubs --incremental --export-all myproject/
复制代码
API 版本:
  1. from stubmaker import StubGenerator, GeneratorConfig
  2. cfg = GeneratorConfig(recursive=True, output_dir="./stubs", incremental=True, export_all=True)
  3. gen = StubGenerator(cfg)
  4. gen.generate_package("./myproject")
复制代码

案例3:解析 Cython 编译扩展 .pyd 二进制模块生成存根。注意不能加 --no-types-eval,必须运行时加载模块。
  1. stubmaker generate --python-exec python3 fastcalc.pyd -o ./stubs
复制代码

案例4:过滤模块并排除测试文件。只处理 core.* 模块,排除所有 test_*.py。
  1. stubmaker generate -r --module-filter "^core\." --exclude "test_.*" src/
复制代码

案例5:生成带文档注释和完整类型导入的标准化存根,适合发布给第三方。
  1. stubmaker generate --include-docstrings --add-type-imports --force src/api.py -o dist/stubs
复制代码

案例6:安全静态解析,防止执行恶意代码。使用 --no-types-eval 仅作 AST 分析,但无法处理二进制扩展库。
  1. stubmaker generate --no-types-eval untrusted_lib.py
复制代码

案例7:在自动化打包时生成存根并集成到 dist 中。编写 build_stubs.py:
  1. from stubmaker import StubGenerator, GeneratorConfig
  2. import os
  3. def build_package_stubs():
  4.     cfg = GeneratorConfig(
  5.         recursive=True,
  6.         output_dir="./build/stubs",
  7.         ignore_private=True,
  8.         export_all=True
  9.     )
  10.     gen = StubGenerator(cfg)
  11.     gen.generate_package("./mypackage")
  12.     print("存根生成完成,路径:", os.path.abspath("./build/stubs"))
  13. if __name__ == "__main__":
  14.     build_package_stubs()
复制代码
然后配合 setup.py 的 cmdclass 调用该脚本。

案例8:校验已手写存根与源码类型一致性,严格模式阻断发布。
  1. stubmaker check --strict stubs/core.pyi --mypy-config mypy.ini
复制代码
API 校验:
  1. from stubmaker import StubGenerator, GeneratorConfig
  2. gen = StubGenerator(GeneratorConfig())
  3. result = gen.check_stub("./stubs/core.pyi", strict=True)
  4. if not result.success:
  5.     raise SystemExit("存根与源码类型不匹配")
复制代码

常见错误与解决方案:

错误1:ModuleNotFoundError: No module named ‘stubmaker’。原因:未安装或 pip 环境不对。统一使用 python -m pip install stubmaker 安装。

错误2:Failed to load module xxx.pyd / segmentation fault。原因:Python 版本或位数不匹配,或系统库缺失。解决:使用匹配的解释器 --python-exec,或安装对应运行库;纯 Python 文件可尝试 --no-types-eval。

错误3:Type evaluation disabled, cannot process binary module。原因:同时使用了 --no-types-eval 和解析二进制模块。移除 --no-types-eval 即可。

错误4:Stub file not updated with --incremental but source changed。原因:增量模式保护了手动修改的文件。如需全量更新,加 --force 参数。

错误5:SyntaxError in generated .pyi, invalid type annotation。原因:源码存在元编程或 Python 版本不支持 | 联合类型。手动修正存根,或升级 Python 到 3.10+。

错误6:Recursive scan not working, subfolder stubs missing。原因:忘记加 -r/--recursive。命令行追加 -r,API 设置 recursive=True。

错误7:ImportError: cannot import name ‘StubGenerator’。原因:stubmaker 版本过低,不支持编程 API。升级到最新版。

错误8:Mypy reports “Missing type annotation”。原因:源码无类型注解,自动推导为 Any。建议在源码中补充基础类型注解,或手动修改存根。

使用注意事项:
- 默认模式会导入并执行目标模块,解析不信任的代码时应加 --no-types-eval(仅对纯 Python 有效)。
- 类型推导对元编程、动态属性、monkey patch 不准确,生成后建议人工校对。
- 生产项目建议将 .pyi 放入独立 stubs/ 目录,并添加 py.typed 文件(PEP561)。
- 版本迭代时使用 --incremental 保护手写注释。
- 大型项目开启 --cache-dir 缓存加速,并用 --exclude 过滤无关目录。
- Python 3.7 及以下生成存根会使用 Union[] 兼容写法,而非 |。
- 建议使用 stubmaker 0.12+ 稳定版以获得递归批量生成等特性。
回复

使用道具 举报

发表于 昨天 10:10 | 显示全部楼层

Re: Python stubmaker 存根生成:语法参数、实战案例与常见错误排查

感谢楼主这么详细的整理!stubmaker 确实是对 stubgen 的一个有力补充,特别能处理二进制扩展这点很赞。案例写得清晰实用,尤其是增量更新和 `--no-types-eval` 安全模式,对接手旧项目或处理第三方库时帮助很大。有个小问题想请教:对于动态生成属性(比如 `__getattr__` 返回的)或装饰器修改过的函数签名,stubmaker 是否能正确推导?或者有没有推荐的兜底做法?
回复 支持 反对

使用道具 举报

发表于 昨天 10:10 | 显示全部楼层

Re: Python stubmaker 存根生成:语法参数、实战案例与常见错误排查

感谢楼主这么详细的分享!之前一直用 stubgen 但处理 .pyd 时就头疼,看到 stubmaker 能直接解析二进制扩展太实用了。案例 7 的自动化打包思路特别好,可以直接集成到构建流程里。 顺便请教一下:解析编译扩展时,如果模块依赖其他动态库(比如 dll/so),是否需要额外设置环境变量或参数?还是只要指定目标文件就能自动处理依赖?
回复 支持 反对

使用道具 举报

发表于 昨天 10:10 | 显示全部楼层

Re: Python stubmaker 存根生成:语法参数、实战案例与常见错误排查

感谢楼主分享,非常系统全面的教程。案例3关于Cython编译扩展的存根生成正好是我最近在折腾的,之前用stubgen没法解析.pyd文件,换stubmaker直接解决了。有个小疑问:对于包含多个动态链接库的大项目,如果用`-r`递归扫描,会不会因为循环导入或模块加载顺序出问题?楼主有没有遇到过?
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-6-25 14:13 , Processed in 0.047577 second(s), 17 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部