确保对象列表的大多数pythonic方法仅包含唯一项

时间:2012-05-10 13:54:08

标签: python

我有一个对象列表(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更改为一个唯一项列表 - 无需遍历每个项目并检查它是否已存在于某些临时缓存等中?。

最狡猾的方式是什么?

5 个答案:

答案 0 :(得分:8)

首先,我想强调使用set肯定不是反模式。 set消除O(n)时间内的重复,这是你能做的最好的,并且比将每个项目与其他项目进行比较的天真O(n ^ 2)解决方案更好。它甚至比排序更好 - 事实上,似乎你的数据结构可能甚至没有自然顺序,在这种情况下,排序没有多大意义。

在这种情况下使用集合的问题是您必须定义自定义__hash__方法。其他人说过这个。但是,你是否可以轻易地这样做是一个悬而未决的问题 - 这取决于你没有告诉我们的实际课程的细节。例如,如果上面的Foo对象的任何属性都不可清除,那么创建自定义哈希函数将会很困难,因为您不仅要为Foo个对象编写自定义哈希值,你还必须为每个其他类型的对象编写自定义哈希值!

因此,如果您想要一个确定的答案,您需要告诉我们更多关于您的课程具有哪些属性的信息。但我可以提出一些猜测。

假设可以为Foo个对象编写 ,但也假设Foo个对象是可变的,所以< em>不应该拥有__hash__方法,正如Niklas B.指出的,这是一种可行的方法。创建一个函数freeze,在给定Foo的可变实例的情况下,返回Foo中不可变的数据集合。例如,假设Foo中有dictlist; freeze返回tupletuple 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__(),以便与setfrozenset一起正常使用,因为两者都是在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])