我有一个对象列表(Foo)。 Foo对象有几个属性。 Foo对象的实例等效(等于)Foo对象的另一个实例iff(当且仅当)所有属性都相等。
我有以下代码:
class Foo(object):
def __init__(self, myid):
self.myid=myid
def __eq__(self, other):
if isinstance(other, self.__class__):
print 'DEBUG: self:',self.__dict__
print 'DEBUG: other:',other.__dict__
return self.__dict__ == other.__dict__
else:
print 'DEBUG: ATTEMPT TO COMPARE DIFFERENT CLASSES:',self.__class__,'compared to:', other.__class__
return False
import copy
f1 = Foo(1)
f2 = Foo(2)
f3 = Foo(3)
f4 = Foo(4)
f5 = copy.deepcopy(f3) # overkill here (I know), but needed for my real code
f_list = [f1,f2,f3,f4,f5]
# Surely, there must be a better way? (this dosen't work BTW!)
new_foo_list = list(set(f_list))
在处理简单类型(int,float,string - 以及令人惊讶的datetime.datetime类型)时,我经常使用上面的这个小(反?)'模式'(转换为set和back),但它已成为一个cropper与更多涉及的数据类型 - 如上面的Foo。
那么,我怎样才能将上面的列表f1更改为一个唯一项列表 - 无需遍历每个项目并检查它是否已存在于某些临时缓存等中?。
最狡猾的方式是什么?
答案 0 :(得分:8)
首先,我想强调使用set
肯定不是反模式。 set
消除O(n)时间内的重复,这是你能做的最好的,并且比将每个项目与其他项目进行比较的天真O(n ^ 2)解决方案更好。它甚至比排序更好 - 事实上,似乎你的数据结构可能甚至没有自然顺序,在这种情况下,排序没有多大意义。
在这种情况下使用集合的问题是您必须定义自定义__hash__
方法。其他人说过这个。但是,你是否可以轻易地这样做是一个悬而未决的问题 - 这取决于你没有告诉我们的实际课程的细节。例如,如果上面的Foo
对象的任何属性都不可清除,那么创建自定义哈希函数将会很困难,因为您不仅要为Foo
个对象编写自定义哈希值,你还必须为每个其他类型的对象编写自定义哈希值!
因此,如果您想要一个确定的答案,您需要告诉我们更多关于您的课程具有哪些属性的信息。但我可以提出一些猜测。
假设可以为Foo
个对象编写 ,但也假设Foo
个对象是可变的,所以< em>不应该拥有__hash__
方法,正如Niklas B.指出的,这是一种可行的方法。创建一个函数freeze
,在给定Foo
的可变实例的情况下,返回Foo
中不可变的数据集合。例如,假设Foo中有dict
和list
; freeze
返回tuple
个tuple
tuple
个(代表dict
)和另一个tuple
(代表list
)。函数freeze
应具有以下属性:
freeze(a) == freeze(b)
当且仅当
a == b
现在通过以下代码传递您的列表:
dupe_free = dict((freeze(x), x) for x in dupe_list).values()
现在你有一个O(n)时间的欺骗免费列表。 (确实,在添加这个建议之后,我看到fraxel提出了类似的建议;但我认为使用自定义函数 - 甚至是方法 - (x.freeze(), x)
- 是更好的方法,而不是而不是像他一样依赖__dict__
,这可能是不可靠的。对于你的自定义__eq__
方法也一样,IMO - __dict__
并不总是一个安全的捷径,因为各种原因我可以'进入这里。)
另一种方法是首先只使用不可变对象!例如,您可以使用namedtuple
s。这是从python docs中窃取的一个例子:
>>> Point = namedtuple('Point', ['x', 'y'])
>>> p = Point(11, y=22) # instantiate with positional or keyword arguments
>>> p[0] + p[1] # indexable like the plain tuple (11, 22)
33
>>> x, y = p # unpack like a regular tuple
>>> x, y
(11, 22)
>>> p.x + p.y # fields also accessible by name
33
>>> p # readable __repr__ with a name=value style
Point(x=11, y=22)
答案 1 :(得分:3)
您是否尝试使用set
(或frozenset
)?它明确地用于保存一组唯一的项目。
但是,您需要创建适当的__hash__
方法。 set
(和frozenset
)使用__hash__
方法来散列对象; __eq__
仅用于碰撞,AFAIK。因此,您需要使用hash(frozenset(self.__dict__.items()))
等散列。
答案 2 :(得分:3)
根据the documentation,您需要为自定义类定义__hash__()
和__eq__()
,以便与set
或frozenset
一起正常使用,因为两者都是在CPython中使用哈希表实现。
如果您实施__hash__
,请注意,如果a == b
,则hash(a)
必须等于hash(b)
。而不是比较整个__dict__
,我建议你的简单类更简单的实现:
class Foo(object):
def __init__(self, myid):
self.myid = myid
def __eq__(self, other):
return isinstance(other, self.__class__) and other.myid == self.myid
def __hash__(self):
return hash(self.myid)
如果您的对象包含可变属性,则不应将其放在集合中或将其用作字典键。
答案 3 :(得分:1)
这是另一种方法,只需为实例__dict__.items()
创建一个字典:
f_list = [f1,f2,f3,f4,f5]
f_dict = dict([(tuple(i.__dict__.items()), i) for i in f_list])
print f_dict
print f_dict.values()
#output:
{(('myid', 1),): <__main__.Foo object at 0xb75e190c>,
(('myid', 2),): <__main__.Foo object at 0xb75e184c>,
(('myid', 3),): <__main__.Foo object at 0xb75e1f6c>,
(('myid', 4),): <__main__.Foo object at 0xb75e1cec>}
[<__main__.Foo object at 0xb75e190c>,
<__main__.Foo object at 0xb75e184c>,
<__main__.Foo object at 0xb75e1f6c>,
<__main__.Foo object at 0xb75e1cec>]
这样你就可以让字典根据属性来处理唯一性,并且可以通过获取值来轻松检索对象。
答案 4 :(得分:-1)
如果您被允许,可以使用一组http://docs.python.org/library/sets.html
list = [1,2,3,3,45,4,45,6]
print set(list)
set([1, 2, 3, 4, 6, 45])
x = set(list)
print x
set([1, 2, 3, 4, 6, 45])