Mypy 报告与重写方法不兼容的超类型错误

Tom*_*ips 4 types python-3.x mypy

下面是我遇到的一个问题的简化示例mypy。该A.transform方法采用对象的可迭代对象,转换每个对象(在子类中定义B,并可能在其他子类中定义)并返回已转换对象的可迭代对象。

from typing import Iterable, TypeVar

T = TypeVar('T')

class A:
    def transform(self, x: Iterable[T]) -> Iterable[T]:
        raise NotImplementedError()

class B(A):
    def transform(self, x: Iterable[str]) -> Iterable[str]:
        return [x.upper() for x in x]
Run Code Online (Sandbox Code Playgroud)

然而mypy说:

error: Argument 1 of "transform" incompatible with supertype "A"
error: Return type of "transform" incompatible with supertype "A"
Run Code Online (Sandbox Code Playgroud)

如果我[T]从 中删除A.transform(),那么错误就会消失。但这似乎是错误的解决方案。

在阅读了covariance 和 contravariance 之后,我认为设置 T = TypeVar('T', covariant=True)可能是一个解决方案,但这会产生相同的错误。

我怎样才能解决这个问题?我已经考虑过将设计完全合并并用更高阶的函数替换 A 类。

Mic*_*x2a 7

T在这种情况下,使协变或逆变并不能真正帮助您。假设您的问题中的代码被 mypy 允许,并假设用户编写了以下代码片段:

def uses_a_or_subclass(foo: A) -> None:
    # This is perfectly typesafe (though it'll crash at runtime)
    print(a.transform(3))

# Uh-oh! B.transform expects a str, so we just broke typesafety!
uses_a_or_subclass(B())  
Run Code Online (Sandbox Code Playgroud)

要记住的黄金法则是,当您需要覆盖或重新定义一个函数时(例如,在子类化时,就像您正在做的那样),该函数的参数是逆变的,并且它们的返回类型是协变的。这意味着当你重新定义一个函数时,使参数更广泛/原始参数类型的超类是合法的,但不是子类型。

一种可能的解决方法是使您的整个类相对于T. 然后,不是子类化A(现在等同于子类化A[Any],如果您想保持完美的类型安全,这可能不是您想要的),而是将A[str].

现在,您的代码是完全类型安全的,并且您重新定义的函数尊重函数差异:

from typing import Iterable, TypeVar, Generic

T = TypeVar('T')

class A(Generic[T]):
    def transform(self, x: Iterable[T]) -> Iterable[T]:
        raise NotImplementedError()

class B(A[str]):
    def transform(self, x: Iterable[str]) -> Iterable[str]:
        return [x.upper() for x in x]
Run Code Online (Sandbox Code Playgroud)

现在,我们uses_a_or_subclass上面的函数应该被重写为泛型,或者接受特定的子类型类A[str]。无论哪种方式都有效,这取决于您要尝试做什么。

  • @SebastianWagner——不幸的是,在那种情况下,我认为你在这里没有什么好的前进方向。安装 'typing' backport 是将完整的类型生态系统引入旧版本 Python 的规范和官方方式。如果您决定不安装它,您自然会遇到这些限制。 (2认同)