Mypy 报告重写方法不兼容的超类型错误
Mypy reports an incompatible supertype error with overridden method
下面是我在使用 mypy
时遇到的问题的简化示例。
A.transform
方法采用对象的可迭代对象,转换每个对象(在 subclass B
和潜在的其他 subclasses 中定义)和 returns 一个可迭代的转换对象。
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]
但是 mypy
说:
error: Argument 1 of "transform" incompatible with supertype "A"
error: Return type of "transform" incompatible with supertype "A"
如果我从 A.transform()
中删除 [T]
,那么错误就会消失。但这似乎是错误的解决方案。
阅读 covariance and contravariance 后,我认为设置
T = TypeVar('T', covariant=True)
可能是一个解决方案,但这会产生相同的错误。
我该如何解决这个问题?我考虑过将设计完全合并并用更高阶函数替换 A class。
使 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())
要记住的黄金法则是,当您需要覆盖或重新定义一个函数时(例如,当子classing,就像您正在做的那样)时,该函数是逆变的 在参数中,covariant 在它们的 return 类型中。这意味着当你重新定义一个函数时,将参数设置为原始参数类型的 broad/a superclass 是合法的,但不是子类型。
一个可能的解决方法是使您的 整个 class 相对于 T
通用。然后,而不是 subclassing A
(现在相当于 subclassing A[Any]
如果你想保持完全类型安全,可能不是你想要的), 你会 subclass 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]
现在,我们上面的 uses_a_or_subclass
函数应该重写为通用的,或者专门接受 class 子类型 A[str]
。两种方式都可行,具体取决于您要执行的操作。
下面是我在使用 mypy
时遇到的问题的简化示例。
A.transform
方法采用对象的可迭代对象,转换每个对象(在 subclass B
和潜在的其他 subclasses 中定义)和 returns 一个可迭代的转换对象。
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]
但是 mypy
说:
error: Argument 1 of "transform" incompatible with supertype "A"
error: Return type of "transform" incompatible with supertype "A"
如果我从 A.transform()
中删除 [T]
,那么错误就会消失。但这似乎是错误的解决方案。
阅读 covariance and contravariance 后,我认为设置
T = TypeVar('T', covariant=True)
可能是一个解决方案,但这会产生相同的错误。
我该如何解决这个问题?我考虑过将设计完全合并并用更高阶函数替换 A class。
使 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())
要记住的黄金法则是,当您需要覆盖或重新定义一个函数时(例如,当子classing,就像您正在做的那样)时,该函数是逆变的 在参数中,covariant 在它们的 return 类型中。这意味着当你重新定义一个函数时,将参数设置为原始参数类型的 broad/a superclass 是合法的,但不是子类型。
一个可能的解决方法是使您的 整个 class 相对于 T
通用。然后,而不是 subclassing A
(现在相当于 subclassing A[Any]
如果你想保持完全类型安全,可能不是你想要的), 你会 subclass 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]
现在,我们上面的 uses_a_or_subclass
函数应该重写为通用的,或者专门接受 class 子类型 A[str]
。两种方式都可行,具体取决于您要执行的操作。