为什么不应该将重复的对象添加到我的Python集中?

时间:2015-02-26 08:39:43

标签: python set

我有一个类的定义:

class Question:
    title = ""
    answer = ""
    def __init__(self, title, answer):
            self.title = title
            self.answer = answer
    def __eq__(self, other):
            return self.title == other.title and self.answer == other.answer
    def __hash__(self):
        return hash(repr(self))

并且我正在尝试将许多这些对象添加到集合中,前提是该对象与该集合中已有的任何其他对象不具有相同的属性:

        questionset = set()
        q = Question(questionparts[0] + questionparts[1], questionparts[2])
        if q not in questionset:
            questionset.add(q)

如果我有两个问题,每个问题具有相同的属性值,我希望只有一个问题可以添加到我的集合中,而我的集合的长度为2.

我做错了什么?如果我记录每个问题对象,我可以确认这些项具有相同的属性值。

4 个答案:

答案 0 :(得分:4)

您的哈希函数严重无效。 Python要求你的__hash__函数应该为两个被认为相等的对象返回相同的值,但是你的函数不会。来自object.__hash__ documentation

  

唯一需要的属性是比较相等的对象具有相同的哈希值

repr(self)返回默认表示,该表示使用对象ID 。它基本上会根据对象标识返回不同的哈希值。你可能也做过:

return hash(id(self))

这不是一个好的哈希,因为所有实例之间的值都不同。因此,您的hash()值无法满足所需的属性:

>>> a = Question('foo', 'bar')
>>> b = Question('foo', 'bar')
>>> a == b
True
>>> hash(a) == hash(b)
False

您需要改为使用哈希属性:

return hash(self.title + self.answer)

现在哈希基于相同的值来通知相等。

答案 1 :(得分:1)

__hash__的{​​{3}}说:

  

唯一需要的属性是比较相等的对象具有相同的哈希值

您的课程不符合此要求。由于您不会覆盖__repr__repr(self)将使用默认行为,这会为您提供<Question object at 0x00001234>之类的内容。这意味着每个Question实例将具有不同的哈希值 - 即使是比较相等的实例。

您需要重写__hash__以与__eq__兼容。如果您在标题/答案相等方面进行比较,最简单的解决方案是对对象的标题和答案进行哈希处理,例如返回hash((self.title, self.answer))

答案 2 :(得分:1)

正如Martijn Pieters和BrenBarn所提到的,由于默认__hash__()方法的行为,您的__repr__()方法无效。他们的答案显示了__hash__()的替代实施;相反,下面的代码实现了一个简单但有用的__repr__()

声称我的方法比Martijn或BrenBarn的方法更好。我只是简单地表明,如果您的班级有合适的__hash__()方法,您的原始__repr__()方法就可以使用。

FWIW,为您的类实现__repr__()方法通常是个好主意。即使您实际上不需要最终生产代码中的方法,在开发代码时也可以帮助您进行测试。

我在Python 2.6.6上运行它,因此from __future__ importobject的显式继承;您不需要在Python 3中执行这些操作,但代码仍应在Python 3上正常运行。

#!/usr/bin/env python

from __future__ import print_function

class Question(object):
    def __init__(self, title, answer):
        self.title = title
        self.answer = answer

    def __repr__(self):
        return 'Q({s.title!r}, {s.answer!r})'.format(s=self)

    def __eq__(self, other):
        return self.title == other.title and self.answer == other.answer

    def __hash__(self):
        return hash(repr(self))

questionset = set()

q1 = Question('2+2', '4')
q2 = Question('2+2', '4')
q3 = Question('2*2', '4')

print(q1, q2, q3)
print(q1 == q2, q1 == q3)

questionset.update((q1, q2, q3))
print(questionset)

questionset.add(Question('1+3', '4'))
questionset.add(Question('2+3', '5'))
questionset.add(Question('2*3', '6'))
questionset.add(Question('1+3', '4'))
print(questionset)

<强>输出

Q('2+2', '4') Q('2+2', '4') Q('2*2', '4')
True False
set([Q('2+2', '4'), Q('2*2', '4')])
set([Q('2+3', '5'), Q('2+2', '4'), Q('1+3', '4'), Q('2*3', '6'), Q('2*2', '4')])

请注意,无需测试Question实例是否已成为questionset的成员。设置元素是唯一的,因此不可能多次添加相同的元素。

答案 3 :(得分:0)

您正在散列对象而不是值。尝试哈希self.title+self.answer而不是散列self