如何对比Python Stub文件判定包接口变化以适配语义化版本控制?
我需要确定Python包公共接口在新旧版本间的变化,以此来辅助**语义化版本(MAJOR.MINOR.PATCH)**的管理。核心思路是通过stubgen生成带类型信息的公共接口stub文件(.pyi格式),再对比新旧版本的stub文件来判定版本变更类型:
- 无接口变化:PATCH号+1
- 原接口不变但新增功能/属性:MINOR号+1
- 原接口有变更(破坏性修改):MAJOR号+1
实现步骤拆解
1. 生成标准化Stub文件
首先对新旧版本的包分别使用stubgen生成完整的stub文件集合,确保包含所有包、子包及模块的公共接口,且保留完整的类型注解信息。比如针对目标包执行:
stubgen -o ./stubs_old your_package_name # 旧版本 stubgen -o ./stubs_new your_package_name # 新版本
生成的stub文件会完整还原类、方法、属性、导入依赖等公共接口的类型定义,比如示例中的positioning.point.pyi:
Stubs for positioning.point (Python 3.6)NOTE: This dynamically typed stub was automatically generated by stubgen.import numpy as np
from .exceptions import UnacceptableCartesianCoordinates
from .frame import Frame
from .methods import check_frames_have_common_parent, get_coordinates_of_point_in_frame, lowest_common_parent
from typing import Any
class Point:
def init(self, frame: Frame, point_coordinates: np.ndarray) -> None: ...
@classmethod
def from_cartesian(cls: Any, frame: Frame, x: float, y: float, z: float) -> Point: ...
# ... 其他方法与属性
2. 解析Stub文件为结构化数据
直接对比文本文件容易受格式(比如换行、注释顺序)影响,所以需要将stub文件解析成包含接口元信息的结构化数据(比如字典或自定义类实例)。可以借助typed_ast来解析带类型注解的Python语法,提取关键信息:
- 模块级导入、导出的符号
- 类的定义:类名、继承关系、属性、方法(包括装饰器、参数类型、返回类型)
- 函数/常量的定义:名称、参数类型、返回类型
解析时需要过滤掉stub文件中的自动生成注释(比如# NOTE:开头的内容),只保留核心接口定义。
3. 对比结构化接口数据
基于解析后的结构化数据,按照以下规则进行对比,判定版本变更类型:
对比规则与对应版本变更
- PATCH版本(无变化):新旧版本的结构化接口数据完全一致,没有新增、修改或删除的接口元素
- MINOR版本(新增功能):新版本仅存在新增的接口元素(类、方法、属性、函数等),旧版本的所有接口元素在新版本中完整保留且未修改
- MAJOR版本(破坏性变更):存在以下任意一种情况
- 旧版本中的接口元素(类、方法、属性等)在新版本中被移除
- 旧版本中的接口元素发生了破坏性修改:比如方法参数数量/类型变化、返回类型变化、属性类型变化、类继承关系变化等
具体对比逻辑
可以将对比拆解为几个维度:
- 模块结构对比:检查子包、模块的新增/删除
- 符号导出对比:检查模块级导出的函数、类、常量的新增/删除
- 类内部对比:检查类的方法、属性的新增/删除,以及方法签名(参数、返回类型)的修改
- 函数/常量对比:检查函数签名、常量类型/值的修改
4. 工具开发建议
如果要自行开发对比工具,可以按照以下流程搭建:
- Stub生成模块:封装
stubgen的调用逻辑,自动为新旧版本包生成stub文件 - AST解析模块:使用
typed_ast编写解析器,将.pyi文件解析为结构化数据 - 对比引擎模块:实现上述的多维度对比逻辑,输出变更类型及具体变更内容
- 结果输出模块:将对比结果以清晰的格式输出(比如控制台报告、JSON文件),明确标注是MAJOR/MINOR/PATCH级别的变更
内容的提问来源于stack exchange,提问作者henryJack

