如何在Python中继承和扩展列表对象?

75

我想使用Python的列表对象,但需要略微修改其功能。特别是,我希望将列表从0索引更改为1索引。例如:

>> mylist = MyList()
>> mylist.extend([1,2,3,4,5])
>> print mylist[1]

输出应为:1

但是当我更改了__getitem__()__setitem__()方法以进行此操作时,我收到了一个RuntimeError: maximum recursion depth exceeded错误。 我尝试了很多调整这些方法的方法,但基本上就是我在里面写的:

class MyList(list):
    def __getitem__(self, key):
        return self[key-1]
    def __setitem__(self, key, item):
        self[key-1] = item
我猜问题在于self[key-1]正在调用它自己定义的同一个方法。如果是这样,我该如何让它使用list()方法而不是MyList()方法?我尝试使用super[key-1]代替self[key-1],但出现了错误信息TypeError: 'type' object is unsubscriptable。有什么想法吗?如果您能指导我学习这方面的好教程,那就太棒了!谢谢!

22
这相当明显地违反了Liskov替换原则。如果你不能在任何期望一个list的地方使用它,那么子类化list可能并没有太多价值。也许在这种情况下,采用组合会是更合适的策略? - Ken
不太明白,你所说的“组合”是什么意思?此外,我们如何确保我们不能用MyList替换标准列表?有没有好的方法可以告诉我们?例如,如果内部方法使用__getitem____setitem__,那么会有问题吗? - mindthief
7
mindthief: http://en.wikipedia.org/wiki/Composition_over_inheritance -- 并且如果您更改list的现有接口(即您在此处要求的),那么就无法将MyList替换为list。即使是像x[0]这样基本的事情也无法正常工作。 - Ken
4个回答

75

使用super()函数调用基类的方法,或直接调用该方法:

class MyList(list):
    def __getitem__(self, key):
        return list.__getitem__(self, key-1)
或者
class MyList(list):
    def __getitem__(self, key):
        return super(MyList, self).__getitem__(key-1)

然而,这不会改变其他列表方法的行为。例如,索引仍然保持不变,这可能导致意外的结果:

numbers = MyList()
numbers.append("one")
numbers.append("two")

print numbers.index('one')
>>> 1

print numbers[numbers.index('one')]
>>> 'two'

4
另外,要注意其他列表方法的破坏,因为你正在改变索引行为,而这是列表对象的基本部分。 - Gintautas Miliauskas
1
如果您注明递归发生的原因,我会点赞。您提供了解决方案,但没有解释问题。 - Nathan Ernst
9
问题中已经很好地解释了为什么会出现递归。 - Peter Milley
3
这里需要指出一个坑:你可能会认为super(MyList, self)[key-1]是可行的,但事实并非如此。super()无法与任何"隐式查找"(例如[]而不是__getitem__)一起使用。 - Peter Milley
注意其他列表方法可能会出现问题。这些方法内部是否使用了__getitem____setitem__ - mindthief
@mindthief:如果他们正在使用__getitem__,他们可以将其访问为self.__getitem__,这将调用您的实现。 - Gintautas Miliauskas

34

相反,使用相同的方法子类化整数,将所有数字设置为比你设定的数字都要小一。就这样。

抱歉,我不得不这么说。这就像微软把暗定义为标准一样好笑。


10
微软将暗作为标准的笑话:“问:微软需要多少硬件工程师来换一颗灯泡?答:不需要,他们把黑暗重新定义为一个行业标准。” - wjandrea

27

你可以通过创建一个继承自 collections.MutableSequence 的类来避免违反 Liskov替换原则。这是一个抽象类。代码大概是这个样子的:

def indexing_decorator(func):
    def decorated(self, index, *args):
        if index == 0:
            raise IndexError('Indices start from 1')
        elif index > 0:
            index -= 1
        return func(self, index, *args)
    return decorated


class MyList(collections.MutableSequence):
    def __init__(self):
        self._inner_list = list()

    def __len__(self):
        return len(self._inner_list)

    @indexing_decorator
    def __delitem__(self, index):
        self._inner_list.__delitem__(index)

    @indexing_decorator
    def insert(self, index, value):
        self._inner_list.insert(index, value)

    @indexing_decorator
    def __setitem__(self, index, value):
        self._inner_list.__setitem__(index, value)

    @indexing_decorator
    def __getitem__(self, index):
        return self._inner_list.__getitem__(index)

    def append(self, value):
        self.insert(len(self) + 1, value)

x = MyList(); x.append(4); y = MyList(); print(y) 试一下。你会得到有趣的结果... - zondo
1
快了。您不应该调整“insert”的索引。另外,请尝试处理索引等于0的情况。 - Hai Vu
是的,你说得对。但这样索引就不一致了。我稍微编辑了答案中的代码。现在应该可以正常工作了,我想。 - Aleksandar Jovanovic
如果从collections.UserList继承,例如:class MyList(collections.UserList),是否会违反Liskov替换原则? - warren.sentient
如果你有类似于 for x in thelist: 的代码,并且列表包含3个项目,那么你将会调用 __getitem__(self, index),其中index为4,导致一个超出范围的错误。也许有人可以验证这一点并提供修复方法? - tpc1095

-8
class ListExt(list):
    def extendX(self, l):
        if l:
            self.extend(l)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接