我应该继承列表还是创建一个包含列表属性的类?

11 投票
2 回答
8115 浏览
提问于 2025-04-18 17:24

我需要一个容器,可以收集多个对象,并且提供一些关于这些对象的报告功能。简单来说,我想做到:

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!"
        

另外,我也可以创建一个自己的类,里面有一个魔法列表属性,但如果我想通过以下方式访问列表,我就得自己写新的方法来添加、扩展等等:

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!"

我觉得从列表继承应该很简单。但这篇文章让它听起来不太好。为什么呢?

2 个回答

6

一般来说,当你在想“我应该继承这个类型,还是把它作为一个成员来用”时,建议你选择不继承。这个原则叫做“更倾向于组合而不是继承”。

这么做的原因是:组合适合用来利用另一个类的功能;而继承适合在其他代码需要使用你正在创建的类和另一个类的功能时使用。

14

扩展列表可能有一个坏处,就是会让你的'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等方法来解决这个问题,这样就不会改变接口,但你不会清楚哪些列表方法实际上被使用,除非你逐行阅读整个代码库。不过,如果你使用组合,只需将列表作为一个字段,并为你支持的操作创建方法,你就能清楚知道需要改变什么。

我最近在工作中遇到了一个和你类似的场景。我有一个对象,里面包含了一些“东西”,最开始我用列表来表示。随着项目需求的变化,我最终把这个对象改成了内部使用字典、一个自定义集合对象,然后快速地改成了OrderedDict。根据我的经验,组合比继承更容易改变某个东西的实现方式。


不过,我认为在某些情况下,扩展列表可能是可以的,尤其是当你的'MagicReport'对象在名称上是个列表时。如果你确实想让'MagicReport'在所有方面都像列表一样使用,并且不打算改变它的实现,那么继承列表可能会更方便。

不过在这种情况下,直接使用列表并写一个'report'函数可能更好——我想象不到你会需要多次报告列表的内容,专门为这个目的创建一个自定义对象和方法可能有点过头(当然,这也取决于你具体想做什么)。

撰写回答