【问题标题】:Refactor: What is the type-safe way to overload methods with instance arguments, e.g. method(self, other)?重构:用实例参数重载方法的类型安全方法是什么,例如方法(自己,其他)?
【发布时间】:2018-11-18 11:00:09
【问题描述】:

我想将以下内容重构为类型安全的内容。我现在给出了一个 mypy “与超类型不兼容”的错误。

我知道这是由于 Liskov 替换原则:

  • 子类型中方法参数的逆变。
  • 子类型中返回类型的协方差。

也就是说(如果我理解正确的话),我可以返回AA子类型,但我只能通过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)

唯一想到的是AB 的父类型,没有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


【解决方案1】:

我可能不会让B 继承自A。也就是说,对于B 应该从A 继承的情况,您可能正在寻找您在Java 的Comparable 中看到的那种模式,其中类将子类作为类型参数:

from abc import ABCMeta, abstractmethod
from typing import Generic, TypeVar

T = TypeVar('T')

class A(Generic[T], metaclass=ABCMeta):
    @abstractmethod
    def add(self, other: T) -> T:
        ...

class B(A['B']):
    def add(self, other: 'B') -> 'B':
        ...

请注意,我已将A 标记为抽象,并将add 标记为抽象方法。在这种情况下,将add 的根声明作为具体方法,或者对于具有具体add 的类将另一个具有具体add 的类的子类化没有多大意义。

【讨论】:

    【解决方案2】:

    如果您想保留现有的类层次结构,您可能应该修改 B 的 add 方法,以便它在接受 A 的某些实例时表现合理。例如,可以执行以下操作:

    from typing import overload, Union
    
    class A:
        def __init__(self, a: int) -> None:
            self.a = a
    
        def add(self, other: A) -> A:
            return A(self.a + other.a)
    
    
    class B(A):
        def __init__(self, a: int, b: int) -> None:
            super().__init__(a=a)
            self.b = b
    
        # Use overloads so we can return a more precise type when the
        # argument is B instead of `Union[A, B]`.
        @overload
        def add(self, other: B) -> B: ...
        @overload
        def add(self, other: A) -> A: ...
    
        def add(self, other: A) -> Union[A, B]:
            if isinstance(other, B):
                return B(self.a + other.a, self.b + other.b)
            else:
                return A(self.a + other.a)
    
    b1 = B(1, 2)
    b2 = B(3, 4)
    a = A(5)
    
    reveal_type(b1.add(b2))  # Revealed type is B
    reveal_type(b1.add(a))   # Revealed type is A
    
    # You get this for free, even without the overloads/before making
    # any of the changes I proposed above.
    reveal_type(a.add(b1))   # Revealed type is A
    

    这会恢复 Liskov 和类型检查。它还使您的 add 方法对称,从可用性的角度来看,这可能是正确的做法。

    如果您想要这种行为(例如,如果 a.add(b1)b1.add(a) 是不可取的),您可能需要重组代码并使用之前建议的 user2357112 方法.与其让 B 继承 A,不如让它包含 A 的 instance 并在必要时委托对它的调用。

    【讨论】:

      猜你喜欢
      • 2021-06-03
      • 2019-11-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多