子类化列表
我想创建一个叫做DataSet的类,简单来说就是一个样本的列表。 不过,我需要在往这个DataSet里添加样本的时候,做一些特别的处理。
有没有什么简单的方法可以做到这一点,而不需要自己去写append、extend、iadd等这些方法呢?
更新:我想给每个样本加一个指针,指向它在DataSet中的位置。这是我用的处理算法所需要的。我有一个解决方案,但感觉不太优雅——就是一个renumber()函数,它确保这些指针是有效的。
1 个回答
我不知道有没有办法做到你想要的——在不重写变更方法的情况下重写它们。不过,使用类装饰器,你可以“自动化”重写的版本(假设每个都可以通过包装基类中的相应方法来实现),所以也不是太麻烦……
比如说,你想做的事情是添加一个“已修改”标志,如果数据自上次调用.save
(这是你用来保存数据并将self.modified
设置为False的方法)以来可能已经改变,这个标志就为真。
那么……:
def wrapMethod(cls, n):
f = getattr(cls, n)
def wrap(self, *a):
self.dirty = True
return f(self, *a)
return wrap
def wrapListMutators(cls):
for n in '''__setitem__ __delitem__ __iadd__ __imul__
append extend insert pop remove reverse sort'''.split():
f = wrapMethod(cls, n)
setattr(cls, n, f)
return cls
@wrapListMutators
class DataSet(list):
dirty = False
def save(self): self.dirty = False
这种语法需要Python 2.6或更高版本,但在早期的Python版本中(那些只支持在def
语句上使用装饰器,而不支持在class
语句上使用装饰器的版本,甚至是一些非常旧的完全不支持装饰器的版本),你只需要更改最后一部分(class
语句),变成:
class DataSet(list):
dirty = False
def save(self): self.dirty = False
DataSet = wrapListMutators(DataSet)
换句话说,漂亮的装饰器语法其实就是在普通函数调用上加了一点语法糖,这个函数以类作为参数并重新分配它。
编辑:现在你已经编辑了你的问题,明确了你的具体需求——在每个项目上维护一个字段bp
,使得对于所有i
,theset[i].bp == i
——这样更容易权衡各种方法的利弊。
你可以调整我之前提到的方法,但在调用被包装的方法之前,不要给self.dirty
赋值,而是在之后调用self.renumber()
,也就是说:
def wrapMethod(cls, n):
f = getattr(cls, n)
def wrap(self, *a):
temp = f(self, *a)
self.renumber()
return temp
return wrap
这样可以满足你所说的要求,但在很多情况下,它会做比必要的更多的工作:例如,当你append
一个项目时,这会不必要地“重新编号”所有现有的项目(变成它们已经有的值)。但是,任何完全自动化的方法怎么能“知道”哪些项目需要重新计算.bp
,而不需要O(N)
的努力呢?至少它必须查看每一个项目(因为你不想单独编写,比如append
和insert
等),这已经是O(N)
了。
所以,只有在每次对列表的更改都可以接受O(N)
的情况下,这种方法才是可以接受的(基本上只有在列表总是保持较小和/或不经常更改的情况下)。
一个更有前景的想法可能是,不要一直维护.bp
值,而是在需要的时候“及时”维护。让bp
成为一个(只读)属性,调用一个方法来检查容器是否“脏”(容器中的“脏”标志是使用我之前给出的自动化代码维护的),然后再重新编号容器(并将其“脏”属性设置为False
)。
当列表通常经历一阵变化时,这种方法会很好用,只有在那时你才需要访问项目的bp
,然后又是一阵变化,等等。在现实世界的容器中,这种变化和读取交替并不少见,但只有你知道这是否适用于你的具体情况!
要想获得更好的性能,我认为你需要在这个通用方法的基础上做一些手动编码,以利用频繁的特殊情况。例如,append
可能会被非常频繁地调用,而在特殊情况下append
所需的工作量非常小,所以写那两三行代码(在这种情况下不设置脏位)可能是值得的。
有一个警告:如果列表中有任何项目出现两次,那么任何方法都将无法工作(实际上你的要求变得自相矛盾)——这当然是完全可能的,除非你采取措施避免这种情况(你可以在renumber
中轻松诊断出来——通过保持一个已经看到的元素集合,并在发现重复时抛出异常——如果这对你来说还不算太晚;如果你要求在导致重复的变更发生时进行“实时”诊断,那就更难了)。也许你可以放宽你的要求,这样如果某个项目出现两次也没关系,bp
可以只指示其中一个索引;或者将bp
变成一个元素出现的索引的集合(这也可以为从不在列表中的元素获取bp
提供一种平滑的方法)。等等等等;我建议你深入考虑(并记录!)所有这些边缘情况——正确性优先于性能!