在python中编写类时,首选的mixin或对象属性是什么?

时间:2019-02-21 01:25:33

标签: python python-3.x

当从多个类组成函数时,我看到了以下两种约定:Data1使用mixins直接向子类添加方法,而Data2实例化完成工作的属性对象。

我认为它们是等效的,选择只是样式之一。那正确吗?还是优先于另一个?

class Data1(Reader, Processor, Writer):
   def __init__(self):
       Reader.__init__(self)
       Processor.__init__(self)
       Writer.__init__(self)

   def run(self):
       self.read()
       self.process()
       self.write()

class Data2:
   def __init__(self):
       self.reader = Reader()
       self.processor = Processor()
       self.writer = Writer()

   def run(self):
       self.reader.read()
       self.processor.process()
       self.writer.write()

为了阐明一个特定的示例,我有一个处理管道,其中有各种数据产品需要分别读取(Reader.read()),处理的数据(Processor.process())和随后的数据产品。处理步骤需要写入db(Writer.write())。

更具体地说,请考虑我有多种健身数据类型:

    来自CSV文件的
  1. 心率数据,需要平均 1秒间隔,并转储到数据库中的heart-rate
  2. 需要将json文件中的运行速度数据转换为 mi / hr,然后格式化为html报告的一部分。
  3. 来自的天气数据 需要整天汇总的网络api,然后 发布到另一个网络api。

对于这些“数据产品”中的每一个,都有一个逻辑readprocesswrite管道,我想将其捕获到一个抽象类中,然后可以用作处理未来“数据产品”的一致模板。

在这些示例中,Reader.read()将是一个抽象类,可以读取csv,json或Web API。 Processor.process()执行各种聚合。 Writer.write()会将处理后的数据发送到各个地方。

鉴于此,我不确定最佳结构。

2 个答案:

答案 0 :(得分:2)

我想避免宗教信仰,是因为您可以找到使用一个或另一个的技术理由,但是经验法则是问是A是B还是A有B? em>在前一种情况下,应在后一种情况下使用继承。

例如,彩色正方形数字,而具有颜色。因此,它应该从图形继承并包含颜色。可能会有一些提示:子对象之一具有独立的生命周期(它可能在组合对象中使用之前就已经存在),因此毫无疑问它具有具有关系。另一方面,如果它不能单独存在(抽象类),那么毫无疑问**是a *关系。

但这意味着在不知道您称为ReaderWriterProcessor以及什么数据的情况下,我无法说出我将使用哪种模型。但是,如果ReaderWriter都是具有独立父成员的相同祖先类的子类,那么我将使用组合。如果它们是专门定制的类,它们共享一个共同祖先的成员,那么它更多的是 is 操作,而我会使用继承。

规则是,在可能的情况下,您应该尊重真实对象的语义。毕竟,在深入的代码执行中,使用继承还是复合都没有关系。


顺便说一句,上面讨论的是一般继承与构成问题。严格来说,mixin是一种特殊情况,因为mixin不应保持任何状态,而只能添加方法,因此通常是抽象的。在Python中,mixin是通过继承实现的,但是其他语言可能具有其他实现。但是在Python中,它们是一个典型的例子,它不一定是关系,但确实使用继承。

答案 1 :(得分:1)

使用超类的时间较短(如果正确初始化了基数):

class Data1(Reader, Processor, Writer):
    def __init__(self):
        super().__init__()

    def run(self):
        self.read()
        self.process()
        self.write()

但是许多人发现合成版本更易于使用,因为您无需遍历继承树来查找实现/重写方法的位置。

Wikipedia关于该主题的较长文章非常值得阅读:https://en.wikipedia.org/wiki/Composition_over_inheritance

附录:您的.run()方法可能会更好地实现为

self.write(self.process(self.read()))

然后将其设置为函数会更容易:

def run(reader, processor, writer):
    return writer.write(processor.process(reader.read()))

或例如记录:

def run(reader, processor, writer):
    for data in reader.read():
        log.debug("Read data: %r", data)
        for output_chunk in processor.process(data):
            log.debug("processed %r and got %r", data, output_chunk)
            writer.write(output_chunk)
            log.debug("wrote %r", output_chunk)

并调用它:

run(Reader(), Processor(), Writer())

假设读者正在产生数据,这可能会更加高效,并且为其编写单元测试也非常容易。

最后:您不需要Reader作为csv,json或Web API读取器类的抽象基类。来自Java / C ++的人们倾向于将类和类型以及子类与子类型混合在一起。 {p>中的reader参数的Python类型

def run(reader, processor, writer):

是∀τ≤{read:NONE→DATA},即具有.read(..)的对象类型的所有子类型t都为NONE(None的类型),并返回DATA类型(此处未指定)的值。例如。标准file对象具有这种类型,可以直接传递,而不必编写带有大量样板的包装器类FileReader。顺便说一句,这就是为什么我认为向Python添加功能不足的类型语言是一件很糟糕的事情,但是在这一点上,我意识到我正在偏离主题;-)