2

我有一个对象列表(Foo)。Foo 对象有几个属性。一个 Foo 对象的实例等价于(等于)另一个 Foo 对象的实例,当且仅当当且仅当所有属性都相等。

我有以下代码:

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),但它已经有了更多涉及的数据类型 - 就像上面的 Foo 一样。

那么,我如何将上面的列表 f1 更改为唯一项目列表 - 而不必遍历每个项目并检查它是否已经存在于某些临时缓存等中?

最pythonic的方法是什么?

4

5 回答 5

8

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

在这种情况下使用集合的问题是您必须定义自定义__hash__方法。其他人也说过这个。但是你是否能轻松做到这一点是一个悬而未决的问题——这取决于你没有告诉我们的实际课程的细节。例如,如果Foo上述对象的任何属性不可散列,那么创建自定义散列函数将很困难,因为您不仅要为Foo对象编写自定义散列,还必须编写自定义散列对于所有其他类型的对象!

因此,如果您想要一个确凿的答案,您需要告诉我们更多关于您的班级具有哪些属性的信息。但我可以提供一些推测。

假设可以为对象编写散列函数Foo,但也假设Foo对象是可变的,因此实际上不应该__hash__方法,正如 Niklas B. 指出的那样,这是一种可行的方法。创建一个函数freeze,给定 的可变实例Foo,返回 中数据的不可变集合Foo。例如,假设 Foo 有 adict和 a listfreeze返回tuple包含s 的 a(表示tuple)和另一个(表示)的 a。该函数应具有以下属性:tupledicttuplelistfreeze

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__由于各种原因我无法进入这里,并不总是安全的捷径。)

另一种方法是首先只使用不可变对象!例如,您可以使用namedtuples。这是从 python 文档中窃取的示例:

>>> 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)
于 2012-05-10T14:08:27.710 回答
3

您是否尝试过使用set(或frozenset)?它明确用于保存一组独特的项目。

不过,您需要创建一个适当的__hash__方法。set(and frozenset) 使用该__hash__方法散列对象;__eq__仅用于碰撞,AFAIK。因此,您需要使用像hash(frozenset(self.__dict__.items())).

于 2012-05-10T13:55:55.680 回答
3

根据文档,您需要定义__hash__()and__eq__()以使您的自定义类与setor一起正常工作frozenset,因为两者都是使用 CPython 中的哈希表实现的。

如果您实施__hash__,请记住如果a == b,则hash(a)必须相等hash(b)__dict__我建议为您的简单类使用以下更直接的实现,而不是比较整个s:

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)

如果您的对象包含可变属性,您根本不应该将它放在集合中或将其用作字典键。

于 2012-05-10T14:01:07.467 回答
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>]

这样,您只需让字典根据属性处理唯一性,并且可以通过获取值轻松检索对象。

于 2012-05-10T14:22:24.687 回答
-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])
于 2012-05-10T13:57:47.427 回答