【发布时间】:2018-11-18 11:00:09
【问题描述】:
我想将以下内容重构为类型安全的内容。我现在给出了一个 mypy “与超类型不兼容”的错误。
我知道这是由于 Liskov 替换原则:
- 子类型中方法参数的逆变。
- 子类型中返回类型的协方差。
也就是说(如果我理解正确的话),我可以返回A 或A 的子类型,但我只能通过A 或超类型 的 A(两者都没有 b 属性)添加到 B.add。
所以,我“不能”做我一直在做的事情,我正在寻找一种重构方法(更多代码见下文)。
# python 3.7
class A:
def __init__(self, a: int) -> None:
self.a = a
def add(self, other: "A") -> "A":
return type(self)(a=self.a + other.a)
class B(A):
def __init__(self, a: int, b: int) -> None:
super().__init__(a=a)
self.b = b
def add(self, other: "B") -> "B": # Argument 1 of "add" incompatible with supertype "A"
return type(self)(a=self.a + other.a, b=self.b + other.b)
唯一想到的是A 和B 的父类型,没有add 方法。
class SuperAB:
# doesn't add
pass
class A(SuperAB):
# has add method
pass
class B(SuperAB):
# has add method
pass
这似乎是一团糟,但如果这是“Pythonic”的事情,我会同意的。我只是想知道是否还有其他方法(除了# type: ignore)。
解决方案:
在玩了各种类型错误的“打鼹鼠”之后,我在 StackOverflow 答案的帮助下解决了这个问题:
T = TypeVar("T")
class A(Generic[T]):
def __init__(self, a: int) -> None:
self.a = a
def add(self, other: T) -> "A":
return type(self)(a=self.a + getattr(other, "a"))
class B(A["B"]):
def __init__(self, a: int, b: int) -> None:
super().__init__(a=a)
self.b = b
def add(self, other: T) -> "B":
return type(self)(
a=self.a + getattr(other, "a"), b=self.b + getattr(other, "b")
)
请注意,我不能执行 self.a + other.a,因为我会看到 "T" has no attribute "a" 错误。上述方法可行,但感觉这里的受访者比我知道的更多,所以我接受了他们的真实建议并进行了重构。
我见过的一条建议是正确的:“B 应该拥有一个 A,而不是 一个 A。”我承认这超出了我的理解范围。 B 有 和 int(实际上是两个,B.a 和 B.b)。这些整数将做整数所做的事情,但是,为了B.add(B),我必须以某种方式将这些整数放入另一个B,如果我希望“以某种方式”具有多态性,我就回到我开始的地方.我显然遗漏了一些关于 OOP 的基本知识。
【问题讨论】:
-
为什么B首先要从A继承?
-
@user2357112 只是一个 MWE,实际上 A 是半边数据结构中的“点”类。点可以有几十个属性(硬度、位置、颜色、uv 矢量、表面法线等)。当两个点“添加”在一起时,必须以某种方式协调这些属性。不同的 Point 子类具有不同的属性,因此需要不同的“添加”方法。 Points 还可以做一些其他事情(因此继承),但这些与属性无关。
-
那些可能需要
has-a而不是is-a关系... -
@JoranBeasley,我不相信。可以通过更宽松的 TypeVar 来克服该问题中的问题。这里的错误是(想确认这一点)警告我实际的不良做法。
标签: python python-3.x mypy