如何在通用 class 中使用 __init__ 中的默认值?

How to use default values in __init__ in a Generic class?

当泛型 class 在 init 参数中有默认值时,MyPy 会抱怨类型不匹配的可能性,即使 init 参数定义了类型。

考虑以下最小示例

class IntOrStrReplicator(Generic[IntStrType]):
    def __init__(self, a: IntStrType) -> None:
        self.a = a
    def produce(self) -> IntStrType:
        return self.a

我们期待以下内容

reveal_type(IntOrStrReplicator(4)) # IntOrStrReplicator[int]
reveal_type(IntOrStrReplicator("Hello")) # IntOrStrReplicator[str]
reveal_type(IntOrStrReplicator(4).produce()) # int
reveal_type(IntOrStrReplicator("Hello").produce()) # str

果然,如果 IntStrType

TypeVar("IntStrType", bound=Union[int, str])

TypeVar("IntStrType", int, str)

现在,我只想添加一个默认值 a。例如 def __init__(self, a: IntStrType = 4) -> None: 显然,使用 a=4 创建其中一个 class 没有问题。然而,该线路然后抱怨。如果 TypeVar 作为联合给出,它表示:

Incompatible default for argument "a" (default has type "int", argument has type "T1")

如果 TypeVar 作为两个不同的选项给出,它会说

Incompatible default for argument "a" (default has type "int", argument has type "str")

When a Generic class has a default value in an init parameter, MyPy complains about the possibility of a type mismatch even though the init parameter defines the type.

这里的问题是__init__参数没有定义类型。你可以做类似

的事情
x = IntOrStrReplicator[str]()

其中类型变量绑定到str,但默认值仍然是4

您的代码并不是说 IntOrStrReplicator() 默认为 IntOrStrReplicator[int](4)。您的代码表示 IntOrStrReplicator[int]()IntOrStrReplicator[str]() 都使用默认参数 4IntOrStrReplicator[str](4) 类型错误,这就是您的默认值无效的原因。

我认为没有任何方式可以表达您的意图。


在没有显式类型参数的情况下,类型检查器将尝试推断 类型,但即便如此,它也不只是通过构造函数参数。它还考虑了上下文,因此在以下代码中:

from typing import Generic, TypeVar

T = TypeVar('T')

class Foo(Generic[T]):
    def __init__(self, x: T):
        self.x = x

def f(x: Foo[int]): pass
def g(x: Foo[object]): pass

f(Foo(3))
g(Foo(3))

T 第一个 Foo(3) 推导为 int,第二个 Foo(3) 推导为 object

正如 user2357112 所解释的,问题是 __init__ 有时只定义类型参数,显式类型可能会失败。

要以这种方式表示默认值,您需要将默认选项限制为相应的类型。您可以使用 overloada restriction on self 来完成此操作。例如

class IntOrStrReplicator(Generic[T1]):
    @overload
    def __init__(self: "IntOrStrReplicator[int]") -> None:
        # int variant, only, allows leaving the parameter implied
        ...

    @overload
    def __init__(self: "IntOrStrReplicator[T1]", a:T1) -> None:
        # supplying the parameter requires that the type match.
        ...

    def __init__(self, a = 4) -> None:
        # This function needs programmer care because `a` still isn't checked
        # but consumers of the class take the overload definitions.
        self.a: T1 = a