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

如何为子类中的工厂方法添加类型提示?

子类工厂模式的类型提示与mypy类方法重写报错问题

问题描述

在常见的子类工厂模式中,该如何正确为子类工厂添加类型提示?另外有个疑问:里氏替换原则原本仅适用于实例而非类,为什么mypy(1.8.0版本)会限制子类中类方法的类型标注?

代码示例

from typing import Any, Self

class A:
    def __init__(self, a: int):
        self.a = a

    @classmethod
    def create(cls, a: int, **keywords: Any) -> Self:
        return cls(a)

class B(A):
    def __init__(self, a: int, b: int):
        super().__init__(a)
        self.b = b

    @classmethod
    def create(cls, a: int, b: int, **keywords: Any) -> Self:
        return cls(a, b)

mypy报错信息

test_factory.py:19: error: Signature of "create" incompatible with supertype "A"  [override]
test_factory.py:19: note:      Superclass:
test_factory.py:19: note:          @classmethod
test_factory.py:19: note:          def create(cls, a: int, **keywords: Any) -> B
test_factory.py:19: note:      Subclass:
test_factory.py:19: note:          @classmethod
test_factory.py:19: note:          def create(cls, a: int, b: int, **keywords: Any) -> B
Found 1 error in 1 file (checked 1 source file)

问题解答

为什么mypy会报错?

mypy的检查逻辑是:类方法重写同样需要保证可替换性。假设你有一段代码接受Type[A]类型的参数,调用它的create(a=1)方法,如果传入的是B类,这个调用会因为缺少必填参数b直接崩溃。里氏替换原则虽然最初针对实例,但类作为可调用对象,其方法的签名兼容性同样会影响代码可靠性——mypy正是为了避免这种静态类型下的潜在错误,才会限制子类类方法的签名。

正确的类型提示方案

方案1:将子类新增参数改为强制关键字参数

把b放在*之后,强制调用时必须以关键字参数传递,既满足子类的必填要求,又不破坏父类的调用签名:

from typing import Any, Self

class A:
    def __init__(self, a: int):
        self.a = a

    @classmethod
    def create(cls, a: int, **keywords: Any) -> Self:
        return cls(a)

class B(A):
    def __init__(self, a: int, b: int):
        super().__init__(a)
        self.b = b

    @classmethod
    def create(cls, a: int, *, b: int, **keywords: Any) -> Self:
        return cls(a, b)

这样,调用B.create(1, b=2)是合法的,而依赖父类create签名的代码(比如只传a的调用)会在静态检查阶段就提示缺少b参数,避免运行时错误。

方案2:用TypeVar明确子类的类型扩展

通过绑定父类的TypeVar,让mypy理解子类的create方法是对父类签名的合理扩展:

from typing import Any, TypeVar, Self

T = TypeVar('T', bound='A')

class A:
    def __init__(self, a: int):
        self.a = a

    @classmethod
    def create(cls: type[T], a: int, **keywords: Any) -> T:
        return cls(a, **keywords)

class B(A):
    def __init__(self, a: int, b: int):
        super().__init__(a)
        self.b = b

    @classmethod
    def create(cls: type[T], a: int, b: int, **keywords: Any) -> T:
        return cls(a, b, **keywords)

这个方案更灵活,允许子类扩展位置参数,同时保证返回类型的正确性。

方案3:跳过检查(不推荐)

如果确定不需要父类与子类的类方法兼容,可以用type: ignore跳过mypy的检查,但这会牺牲类型安全性:

class B(A):
    def __init__(self, a: int, b: int):
        super().__init__(a)
        self.b = b

    @classmethod
    def create(cls, a: int, b: int, **keywords: Any) -> Self:  # type: ignore[override]
        return cls(a, b)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 21:33:19