自定义包装器,使Python列表从1开始索引
我想为Python的list
类型写一个简单的封装,让它从1
开始索引,而不是从0
开始。我有一个比较复杂的程序,基于一些离散概率分布的持续时间数据,使用的是整数长度的桶,但我没有小于1的持续时间。总之,如果能从1开始索引,会大大简化我代码中的一些重要部分。我一开始用的是dict
,但发现它的一些特性太麻烦了。
我之前从来没有为Python类写过封装,更别提内置类型了,但我觉得我想做的事情其实挺简单的。至少,我应该能做到这样的操作:
>>> p = one_list([1,2,3,4,5])
>>> for i in range(1,6):
print i, p[i]
1 1
2 2
3 3
4 4
5 5
>>> len(p)
5
不过,如果我能重写一些list
类的其他相关内置方法,比如index
,那就更好了。
>>> len(p)
5
>>> p.index(p[-1])
5
请分享一下你们的建议,告诉我该怎么做。我在考虑是否要用自定义类来实现,但这似乎有点过于复杂。我也欢迎任何关于有用的重写方法的推荐。
编辑:后记
我想说的是,做这个其实不太值得麻烦,之所以接受下面的回答,并不是因为我尝试按照他描述的方式去实现,而是因为他让我意识到,列表本身已经足够好了。
3 个回答
这是一个基本的实现,帮助你入门。我觉得把它做得更通用一些会很好,这样你就可以创建从任何整数开始的列表索引。这让我发现了__slots__
,所以谢谢你提出这个问题!
class OffsetList(list):
__slots__ = 'offset'
def __init__(self, init=[], offset=-1):
super(OffsetList, self).__init__(init)
self.offset = offset
def __getitem__(self, key):
return super(OffsetList, self).__getitem__(key + self.offset)
def __setitem__(self, key, value):
return super(OffsetList, self).__setitem__(key + self.offset, value)
def __delitem__(self, key):
return super(OffsetList, self).__delitem__(key + self.offset)
def index(self, *args):
return super(OffsetList, self).index(*args) - self.offset
>>> a = OffsetList([1,2,3])
>>> a
[1, 2, 3]
>>> a[1]
1
>>> a.index(2)
2
你可能还想实现一下__getslice__
、__setslice__
、__delslice__
,更不用说pop
和insert
了。
好的,看起来你很有决心。正如我之前提到的,你需要重写几个特殊的方法:__getitem__
、__setitem__
和 __delitem__
。实际上,你还需要重写 __getslice__
、__setslice__
和 __delslice__
(否则切片操作会像平常一样从0开始)。接着,你还需要重写 __add__
、__mul__
、__iadd__
和 __imul__
,这样才能返回 WonkyList
(否则拼接操作就不能按你想要的方式工作)。你还得重写 index
,因为它会返回错误的值(我测试过了)。另外,insert
、remove
和 pop
也需要重写。这里有一些可以帮助你入门的内容:
class WonkyList(list):
def __getitem__(self, index):
return super(WonkyList, self).__getitem__(index - 1)
def __setitem__(self, index, val):
super(WonkyList, self).__setitem__(index - 1, val)
def __delitem__(self, index):
super(WonkyList, self).__delitem__(index - 1)
测试过了:
>>> w = WonkyList(range(10))
>>> w[1]
0
>>> del w[5]
>>> w
[0, 1, 2, 3, 5, 6, 7, 8, 9]
>>> w[1] = 10
>>> w
[10, 1, 2, 3, 5, 6, 7, 8, 9]
在你重写所有必要的方法之前,这里有一些可能会出错的情况:
>>> w
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> w[5]
4
>>> w.pop(5)
5
>>> w.insert(5, 5)
>>> w
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> del w[2:4]
>>> w
[0, 1, 4, 5, 6, 7, 8, 9]
>>>
>>> w.index(1)
1
或者你可以尝试其他方法。比如,你有没有考虑过……
def ExtraItemList(list):
def __init__(self, seq):
self[0] = 0
self[1:] = seq
def n_items(self):
return len(self) - 1
这个方法似乎解决了你提到的问题,而且它的行为基本上还是像列表,尽管初始化的方式有点奇怪。不过说实话,看看你给的例子,我开始觉得即使这样也不是个好办法——你给的函数并没有显示从1开始索引的好处,只是展示了在列表开头添加一个额外项的坏影响。根据这些例子,你应该直接使用普通的列表。
或许最好的方法是写自定义的 get
和 set
方法,使用你想要的偏移量。这样列表的行为就会正常,但在需要的时候你可以有一个替代的接口。
这里有一个完整的(我觉得是)基于1的列表实现,它能正确处理切片(包括扩展切片)、索引、弹出元素等功能。其实,要把这些做对比你想象的要复杂一点,特别是切片和负索引的部分。事实上,我现在也不太确定它是否完全按照预期工作,所以 程序员请注意。
class list1(list):
"""One-based version of list."""
def _zerobased(self, i):
if type(i) is slice:
return slice(self._zerobased(i.start),
self._zerobased(i.stop), i.step)
else:
if i is None or i < 0:
return i
elif not i:
raise IndexError("element 0 does not exist in 1-based list")
return i - 1
def __getitem__(self, i):
return list.__getitem__(self, self._zerobased(i))
def __setitem__(self, i, value):
list.__setitem__(self, self._zerobased(i), value)
def __delitem__(self, i):
list.__delitem__(self, self._zerobased(i))
def __getslice__(self, i, j):
print i,j
return list.__getslice__(self, self._zerobased(i or 1),
self._zerobased(j))
def __setslice__(self, i, j, value):
list.__setslice__(self, self._zerobased(i or 1),
self._zerobased(j), value)
def index(self, value, start=1, stop=-1):
return list.index(self, value, self._zerobased(start),
self._zerobased(stop)) + 1
def pop(self, i):
return list.pop(self, self._zerobased(i))
不过,senderle 的 ExtraItemList
性能会更好,因为它不需要不断调整索引,也没有额外的(非C语言的)方法调用层在你和数据之间。我真希望我能想到这种方法;也许我可以把它和我的方法结合起来,创造出更好的效果……