确保对象列表只包含唯一项的最python方法

2024-06-16 12:09:44 发布

您现在位置:Python中文网/ 问答频道 /正文

我有一个对象列表(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)时,以及令人惊讶的日期时间。日期时间类型),但随着更复杂的数据类型(如上面的Foo)的出现,它变得越来越糟糕。在

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

什么是最Python式的方法?在


Tags: 对象实例debugself列表属性foodict
3条回答

根据the documentation,您需要定义__hash__()和{},以便自定义类正确使用set或{},因为这两个都是使用CPython中的哈希表实现的。在

如果实现__hash__,请记住如果a == b,那么{}必须等于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)

如果对象包含可变属性,则不应将其放在集合中或将其用作字典键。在

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

不过,您需要创建一个适当的__hash__方法。set(和frozenset)使用__hash__方法散列对象;__eq__只用于碰撞,AFAIK。相应地,您将希望使用类似hash(frozenset(self.__dict__.items()))的散列。在

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

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

所以如果你想要一个结论性的答案,你需要告诉我们更多关于你的类有什么样的属性。但我可以提供一些推测。在

假设可以为Foo对象编写散列函数,但也假设Foo对象是可变的,因此,正如Niklas B.所指出的,这是一种可行的方法。创建一个函数freeze,给定Foo的可变实例,该函数返回Foo中不可变的数据集合。例如,假设Foo中有一个dict和一个listfreeze返回一个tupletuple(表示dict)和另一个tuple(表示dict)和另一个{}(表示list)。函数freeze应具有以下属性:

freeze(a) == freeze(b)

当且仅当

^{pr2}$

现在通过以下代码传递您的列表:

dupe_free = dict((freeze(x), x) for x in dupe_list).values()

现在你有了一个无重复的列表。(实际上,在添加了这个建议之后,我看到fraxel也提出了类似的建议;但是我认为使用自定义函数甚至是方法(x.freeze(), x)是更好的方法,而不是像他那样依赖__dict__,这可能不可靠。您的自定义__eq__方法也是如此,IMO--__dict__并不总是一个安全的快捷方式,因为各种原因我无法进入这里。)

另一种方法是首先只使用不可变的对象!例如,可以使用^{}s。下面是从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)

相关问题 更多 >