You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

咨询MyPy插件中get_type_analyze_hook返回值及子类规则复现方法

问题背景

我参与开发了科学工作流引擎Pydra,它利用Python类型注解在工作流构建阶段做动态类型检查,同时通过注解识别文件/目录输入,将内容纳入内部缓存的哈希计算中。

为解决带独立头文件的文件格式处理问题,我编写了FileFormats包,用于验证和处理不同文件格式,还支持把格式验证纳入动态类型检查,以及注册不同格式类型间的标准转换器。

在这个包里,我用__class_getitem__来指定文件内容的额外信息,比如Gzip[Png]表示gzip压缩的PNG文件,DicomSeries[Brain, T1Weighted]表示脑部T1加权MRI的DICOM序列。我还在混合类里实现了__subclasshook__,处理子类判定逻辑——比如让DicomSeries[Brain, T1Weighted]被视为DicomSeries[Brain]、DicomSeries[T1Weighted]和DicomSeries的子类。

目前包的运行效果很好,但适配静态类型检查工具Mypy时,发现非泛型类使用__class_getitem__是Mypy不推荐/不支持的功能,会触发警告。

我的问题

如何编写带有get_type_analyze_hook的Mypy插件,消除上述警告,同时复现__subclasshook__里的子类判定规则?另外想明确:get_type_analyze_hook的作用是什么?是不是只要让它返回有效类型就行?能不能复用我现有代码里的逻辑来实现?


解决方案

1. get_type_analyze_hook的核心作用

它是Mypy插件的核心钩子之一,负责拦截并自定义Mypy对特定类型构造的解析过程。当Mypy遇到X[Y,Z]这类类型表达式时,会调用对应插件的这个钩子,你可以在钩子函数里返回一个符合Mypy类型系统规范的类型对象,直接绕过Mypy对非泛型类使用__class_getitem__的默认检查逻辑。

2. 编写插件的核心步骤

步骤1:搭建插件基础结构

创建插件文件(比如fileformats_mypy_plugin.py),实现Mypy插件的入口并注册钩子:

from mypy.plugin import Plugin
from mypy.nodes import TypeExpr
from mypy.types import Type

class FileFormatsPlugin(Plugin):
    def get_type_analyze_hook(self, fullname: str):
        # 仅处理FileFormats包下的类
        if fullname.startswith("fileformats."):
            return self.analyze_fileformat_type
        return None

    def analyze_fileformat_type(self, ctx: TypeExpr) -> Type:
        # 后续实现自定义解析逻辑
        pass

def plugin(version: str):
    return FileFormatsPlugin

步骤2:解析类型参数并返回合法类型

在analyze_fileformat_type中,提取X[Y,Z]的主类型和参数,构造Mypy能识别的合法类型,消除警告:

from mypy.nodes import IndexExpr
from mypy.types import Instance, TupleType

def analyze_fileformat_type(self, ctx: TypeExpr) -> Type:
    # 只处理索引表达式(即X[Y,Z]形式)
    if not isinstance(ctx, IndexExpr):
        return self.named_type(ctx.fullname)
    
    # 获取主类型全名和参数表达式
    base_type_fullname = ctx.base.fullname
    args_expr = ctx.args
    
    # 解析参数为Mypy类型对象
    parsed_args = []
    for arg in args_expr:
        # 处理参数为类型引用的情况
        if hasattr(arg, "fullname"):
            parsed_args.append(self.named_type(arg.fullname))
        # 处理其他参数类型(如字面量)
        else:
            parsed_args.append(self.type_map[arg])
    
    # 构造主类型的Instance对象,传入解析后的参数
    base_type = self.named_type(base_type_fullname)
    return Instance(base_type.type, parsed_args)

步骤3:复现__subclasshook__的子类判定规则

需要额外实现get_subtype_hook钩子,介入Mypy的子类型检查逻辑:

from mypy.subtypes import is_subtype

class FileFormatsPlugin(Plugin):
    # ... 保留之前的get_type_analyze_hook ...

    def get_subtype_hook(self):
        return self.check_fileformat_subtype

    def check_fileformat_subtype(self, left: Type, right: Type) -> bool | None:
        # 仅处理FileFormats包下的Instance类型
        if (isinstance(left, Instance) and isinstance(right, Instance)
            and left.type.fullname.startswith("fileformats.")
            and right.type.fullname.startswith("fileformats.")):
            
            # 主类型不同时,交给Mypy默认逻辑
            if left.type.fullname != right.type.fullname:
                return None
            
            # 实现你的子类判定逻辑:比如右侧参数是左侧参数的子集
            right_arg_names = {arg.type.fullname for arg in right.args}
            left_arg_names = {arg.type.fullname for arg in left.args}
            if right_arg_names.issubset(left_arg_names):
                return True
        # 返回None表示使用Mypy默认检查逻辑
        return None

3. 复用现有代码逻辑

把你现有__subclasshook__中的判定逻辑提取成独立工具函数(比如放在fileformats.utils中),然后在插件中直接导入调用,避免重复代码:

# 在fileformats.utils中定义
def is_format_subtype(left_args: list[type], right_args: list[type]) -> bool:
    # 这里复用你原有的子类判定逻辑
    right_arg_set = set(right_args)
    return right_arg_set.issubset(left_args)

# 在插件中调用
from fileformats.utils import is_format_subtype

def check_fileformat_subtype(self, left: Type, right: Type) -> bool | None:
    # ... 类型检查逻辑 ...
    # 将Mypy类型对象转换为你代码中的实际类型
    left_real_args = [self.lookup_fully_qualified(arg.type.fullname).type for arg in left.args]
    right_real_args = [self.lookup_fully_qualified(arg.type.fullname).type for arg in right.args]
    return is_format_subtype(left_real_args, right_real_args)

4. 启用插件

在项目的mypy.ini或pyproject.toml中配置插件:

# mypy.ini
[mypy]
plugins = fileformats_mypy_plugin

内容的提问来源于stack exchange,提问作者Tom Close

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 04:57:13