为什么不应该将重复的对象添加到我的 Python 集中?
Why is a duplicate object being added to my Python set when it shouldn't be?
我对此有 class 定义:
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。
我做错了什么?如果我记录每个问题对象,我可以确认这些项目具有相同的 属性 值。
您的哈希函数严重无效。 Python 要求您的 __hash__
函数应该 return 为两个被认为相等的对象提供相同的值,但您的函数却没有。来自 object.__hash__
documentation:
The only required property is that objects which compare equal have the same hash value
repr(self)
returns 默认表示,它使用 object id。它基本上 return 一个基于 对象身份 的不同散列。你也可以这样做:
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)
现在哈希基于告知相等性的相同值。
__hash__
的 documentation 说:
The only required property is that objects which compare equal have the same hash value
您的 class 不符合此要求。由于您没有覆盖 __repr__
,repr(self)
将使用默认行为,这会给您类似 <Question object at 0x00001234>
的结果。这意味着每个 Question
个实例将具有不同的哈希值 --- 即使是比较相等的实例。
您需要重写 __hash__
以与 __eq__
兼容。如果您比较 title/answer 相等性,最简单的解决方案是也散列 object 的标题和答案,例如返回 hash((self.title, self.answer))
.
您正在散列对象而不是值。尝试散列 self.title+self.answer
而不是散列 self
正如 Martijn Pieters 和 BrenBarn 所提到的,由于默认 __repr__()
方法的行为,您的 __hash__()
方法不起作用。他们的回答显示了 __hash__()
的替代实现;下面的代码反而实现了一个简单但有用的 __repr__()
。
我没有声称我的方法比 Martijn 或 BrenBarn 的方法更好。我只是想证明,一旦您的 class 有合适的 __repr__()
方法,您原来的 __hash__()
方法就会起作用。
FWIW,为您的 classes 实现一个 __repr__()
方法通常是个好主意。即使您在最终生产代码中实际上不需要该方法,它也有助于在开发代码时进行测试。
我在 Python 2.6.6 上 运行,因此 from __future__ import
和 object
的显式继承;您不需要在 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
的成员。集合元素是唯一的,所以不可能多次添加相同的元素。
我对此有 class 定义:
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。
我做错了什么?如果我记录每个问题对象,我可以确认这些项目具有相同的 属性 值。
您的哈希函数严重无效。 Python 要求您的 __hash__
函数应该 return 为两个被认为相等的对象提供相同的值,但您的函数却没有。来自 object.__hash__
documentation:
The only required property is that objects which compare equal have the same hash value
repr(self)
returns 默认表示,它使用 object id。它基本上 return 一个基于 对象身份 的不同散列。你也可以这样做:
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)
现在哈希基于告知相等性的相同值。
__hash__
的 documentation 说:
The only required property is that objects which compare equal have the same hash value
您的 class 不符合此要求。由于您没有覆盖 __repr__
,repr(self)
将使用默认行为,这会给您类似 <Question object at 0x00001234>
的结果。这意味着每个 Question
个实例将具有不同的哈希值 --- 即使是比较相等的实例。
您需要重写 __hash__
以与 __eq__
兼容。如果您比较 title/answer 相等性,最简单的解决方案是也散列 object 的标题和答案,例如返回 hash((self.title, self.answer))
.
您正在散列对象而不是值。尝试散列 self.title+self.answer
而不是散列 self
正如 Martijn Pieters 和 BrenBarn 所提到的,由于默认 __repr__()
方法的行为,您的 __hash__()
方法不起作用。他们的回答显示了 __hash__()
的替代实现;下面的代码反而实现了一个简单但有用的 __repr__()
。
我没有声称我的方法比 Martijn 或 BrenBarn 的方法更好。我只是想证明,一旦您的 class 有合适的 __repr__()
方法,您原来的 __hash__()
方法就会起作用。
FWIW,为您的 classes 实现一个 __repr__()
方法通常是个好主意。即使您在最终生产代码中实际上不需要该方法,它也有助于在开发代码时进行测试。
我在 Python 2.6.6 上 运行,因此 from __future__ import
和 object
的显式继承;您不需要在 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
的成员。集合元素是唯一的,所以不可能多次添加相同的元素。