我在Python中实现了类的层次 struct .公共基类像接口一样expose 了一些方法,有一些抽象类不应该直接实例化,每个具体子类可以混合一些抽象类并提供额外的行为.例如,下面的代码是我开始的一个简单示例:

class BaseEntity(ABC):
    @property
    @abstractmethod
    def f(self) -> List[str]:
        pass

    @property
    @abstractmethod
    def g(self) -> int:
        pass


class AbstractEntity1(BaseEntity, ABC):  # this should not be instantiable
    @property
    @abstractmethod
    def f(self) -> List[str]:
        return ["a", "b"]

    @property
    @abstractmethod
    def g(self) -> int:
        return 10


class AbstractEntity2(BaseEntity, ABC):  # this should not be instantiable
    @property
    @abstractmethod
    def f(self) -> List[str]:
        return ["x"]

    @property
    @abstractmethod
    def g(self) -> int:
        return 3


class FinalEntity(AbstractEntity1, AbstractEntity2, BaseEntity):

    @property
    def f(self) -> List[str]:
        return ["C"]

    @property
    def g(self) -> int:
        return 10

我希望FinalEntity和所有其他具体实体的行为如下:当我调用final_entity.f()时,它应该返回["a"、"b"、"x"、"C"](因此相当于对每个Mixin和类本身调用+操作符);类似地,当我调用final_entity.g()时,它应该返回10 + 3 + 10(即对每个Mixin和类本身调用+操作符).这些函数显然只是一个示例,并不总是+,因此必须 for each 函数定义它.

解决这个问题的最好的方法是什么?

推荐答案

恭喜!你已经发现了什么时候使用继承.继承是程序员工具包中一个非常具体的工具,它解决了一个非常具体的问题,但是有很多糟糕的教程和糟糕的讲师试图用子类来敲打软件开发世界中的every钉.

你所拥有的是一个list,而不是一系列超类.如果你想要FinalEntityhave种trait ,那就是has-a种关系.我们用composition来模拟has-a种关系,而不是inheritance种.

也就是说,我们没有从它的所有特征中获得FinalEntity inherit个,而是拥有contain个它们.

class Trait1:
    def f(self):
        return ["a", "b"]

class Trait2:
    def f(self):
        return ["x"]

class Trait3:
    def f(self):
        return ["C"]

那么你的FinalEntity就可以简单地

class FinalEntity:

    def __init__(self, traits):
        self.traits = traits

    def f(self):
        return [value for trait in self.traits for value in trait.f()]

作为额外的好处,这使编写测试更容易way.你建议的FinalEntity对它的父母来说是tightly coupled,所以你不可能不嘲笑它的不同特征来测试它.但this FinalEntity有一个构造函数,可以免费直接插入并播放不同的特征.


请注意,如果你想使用继承,你只需要在子类中调用super,Python默认的方法解析顺序将完成剩下的工作.

class BaseEntity:
    def f(self):
        return []

class AbstractEntity1(BaseEntity):
    def f(self):
        return super.f() + ["a", "b"]

class AbstractEntity2(BaseEntity):
    @abstractmethod
    def f(self):
        return super.f() + ["x"]

class FinalEntity(AbstractEntity1, AbstractEntity2):
    def f(self):
        return super.f() + ["C"]

但是,同样,这将导致更脆弱的代码,更难测试.


不管它的价值是什么,你正在寻找的功能被称为method combinations.这在Python中是不可用的特性,但是一些语言,尤其是Common Lisp,开箱即用地支持这一功能.等同于您在Common Lisp中编写的代码是

(defclass base () ())
(defclass abstract1 (base) ())
(defclass abstract2 (base) ())
(defclass final (abstract1 abstract2) ())

(defgeneric f (value)
  (:method-combination append :most-specific-last))

(defmethod f append ((value abstract1))
  (list "a" "b"))

(defmethod f append ((value abstract2))
  (list "x"))

(defmethod f append ((value final))
  (list "C"))

(let ((instance (make-instance 'final)))
  (format t "~A~%" (f instance))) ;; Prints ("a" "b" "x" "C")

但是,再说一次,如果没有lot个聪明的反射技巧,这在Python中是不可用的.

Python相关问答推荐

如何强制cv2.electrical画顺时针弧线?

将数组操作转化为纯numpy方法

使用子字符串动态更新Python DataFrame中的列

预期LP_c_Short实例而不是_ctyles.PyCStructType

如何使用scikit-learn Python库中的Agglomerative集群算法以及集群中声明的对象数量?

使用Python Great Expectations和python-oracledb

在Python中使用一行try

在Transformer中使用LabelEncoding的ML模型管道

指示组内的rejected_time是否在creation_timestamp后5分钟内

在for循环中仅执行一次此操作

使用from_pandas将GeDataFrame转换为polars失败,ArrowType错误:未传递numpy. dype对象

在Python中为变量的缺失值创建虚拟值

返回nxon矩阵的diag元素,而不使用for循环

滚动和,句号来自Pandas列

ThreadPoolExecutor和单个线程的超时

SQLAlchemy bindparam在mssql上失败(但在mysql上工作)

如何启动下载并在不击中磁盘的情况下呈现响应?

如何在Python中获取`Genericums`超级类型?

合并帧,但不按合并键排序

如何在Python中使用Pandas将R s Tukey s HSD表转换为相关矩阵''