我应该继承列表还是创建具有列表作为属性的类?

Posted

技术标签:

【中文标题】我应该继承列表还是创建具有列表作为属性的类?【英文标题】:Should I subclass list or create class with list as attribute? 【发布时间】:2014-10-09 07:24:53 【问题描述】:

我需要一个可以收集大量对象并提供有关容器元素的报告功能的容器。本质上,我希望能够做到:

magiclistobject = MagicList()
magiclistobject.report()  ### generates all my needed info about the list content

所以我想到了对普通列表进行子类化并添加一个 report() 方法。这样,我就可以使用所有内置的列表功能。

class SubClassedList(list):
    def __init__(self):
        list.__init__(self)
    
    
    def report(self):      # forgive the silly example
        if 999 in self:
            print "999 Alert!"
        

相反,我也可以创建自己的具有 magiclist 属性的类,但如果我想使用以下方法访问列表,则必须创建用于追加、扩展等的新方法:

magiclistobject.append() # instead of magiclistobject.list.append()

我需要这样的东西(这似乎是多余的):

class MagicList():
    def __init__(self):
        self.list = []

    def append(self,element):
        self.list.append(element)

    def extend(self,element):
        self.list.extend(element)

# more list functionality as needed...
    
    def report(self):       
        if 999 in self.list:
            print "999 Alert!"

我认为对列表进行子类化是不费吹灰之力的。但是this post here 听起来像是一个禁忌。为什么?

【问题讨论】:

作为另一种选择,您可以完全跳过课程并编写report(some_ordinary_list) 函数。 在决定是否进行子类化时,请牢记Liskov Substitution Principle。 【参考方案1】:

作为一般规则,每当您问自己“我应该继承还是拥有该类型的成员”时,请选择不继承。这条经验法则被称为“优先组合优于继承”。

之所以会这样,是因为:组合适合你想使用另一个类的特性;如果其他代码需要将其他类的功能与您正在创建的类一起使用,则继承是合适的。

【讨论】:

【参考方案2】:

扩展列表可能不好的一个原因是因为它将您的“MagicReport”对象与列表过于紧密地联系在一起。例如,Python 列表支持以下方法:

append
count
extend
index
insert
pop
remove
reverse
sort

它还包含大量其他操作(添加、使用<> 进行比较、切片等)。

您的“MagicReport”对象是否真的想要支持所有这些操作?例如,以下是合法的 Python:

b = [1, 2]
b *= 3
print b   # [1, 2, 1, 2, 1, 2]

这是一个非常人为的例子,但是如果你从“list”继承,如果有人无意中做了类似的事情,你的“MagicReport”对象也会做同样的事情。

再举一个例子,如果您尝试切片您的 MagicReport 对象会怎样?

m = MagicReport()

# Add stuff to m

slice = m[2:3]
print type(slice)

您可能希望切片是另一个 MagicReport 对象,但它实际上是一个列表。您需要覆盖 __getslice__ 以避免令人惊讶的行为,这有点痛苦。


这也使您更难更改 MagicReport 对象的实现。如果您最终需要进行更复杂的分析,通常有助于将底层数据结构更改为更适合该问题的结构。

如果您将列表子类化,您可以通过提供新的appendextend 等方法来解决此问题,这样您就不会更改接口,但您不会除非您通读整个代码库,否则有任何明确的方法可以确定实际使用了哪些列表方法。但是,如果您使用组合并将列表作为字段并为您支持的操作创建方法,那么您确切知道需要更改什么。

实际上,我最近遇到了一个与您的工作非常相似的场景。我有一个对象,其中包含我首先在内部表示为列表的“事物”集合。随着项目需求的变化,我最终将对象更改为在内部使用 dict、自定义集合对象,最后是快速连续的 OrderedDict。至少根据我的经验,与继承相比,组合更容易改变实现方式。


话虽如此,我认为在您的“MagicReport”对象合法地是除名称之外的所有列表的情况下,扩展列表可能是可以的。如果您确实想以各种方式将 MagicReport 用作列表,并且不打算更改其实现,那么将列表子类化并完成它可能会更方便。

虽然在这种情况下,最好只使用一个列表并编写一个“报告”函数——我无法想象你需要多次报告列表的内容,并创建一个自定义对象仅用于此目的的自定义方法可能是矫枉过正(尽管这显然取决于您到底要做什么)

【讨论】:

+1 用于建议功能。无论如何,这就是 Python 做事的方式:例如,len()len 实际上调用了一个对象的__len__ 方法,但它是一个函数,因为它代表了一个鸭子类型的接口,即len 的参数将有一个长度的承诺。对象是在列表还是元组(甚至可能是集合)中并不重要。

以上是关于我应该继承列表还是创建具有列表作为属性的类?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 C++ 中的类创建和填充链表?

Ruby类实例变量和继承

如何在Python中的for循环内创建对象列表而不继承属性值[重复]

预填充核心数据存储:使用 JSON 属性列表还是 XML 文件?

如何在 CloudKit 中创建记录作为孩子的记录列表?

如何删除作为另一个类列表的类属性