我有一组对象,并且对从集合中获取特定对象感兴趣。经过一些研究,我决定使用此处提供的解决方案:http://code.activestate.com/recipes/499299/
问题在于它似乎没有起作用。
我有两个定义的类:
class Foo(object):
def __init__(self, a, b, c):
self.a = a
self.b = b
self.c = c
def __key(self):
return (self.a, self.b, self.c)
def __eq__(self, other):
return self.__key() == other.__key()
def __hash__(self):
return hash(self.__key())
class Bar(Foo):
def __init__(self, a, b, c, d, e):
self.a = a
self.b = b
self.c = c
self.d = d
self.e = e
注意:这两个类的相等性只能在属性a,b,c上定义。
http://code.activestate.com/recipes/499299/中的包装器_CaptureEq
也定义了自己的__eq__
方法。问题是这种方法永远不会被调用(我认为)。考虑一下,
bar_1 = Bar(1,2,3,4,5)
bar_2 = Bar(1,2,3,10,11)
summary = set((bar_1,))
assert(bar_1 == bar_2)
bar_equiv = get_equivalent(summary, bar_2)
bar_equiv.d
应该等于4,同样bar_equiv .e
应该等于5,但它们不是。就像我提到的那样,当执行语句__CaptureEq
时,看起来不会调用__eq__
bar_2 in summary
方法。
有没有理由不调用__CaptureEq
__eq__
方法?希望这不是一个问题的模糊。
答案 0 :(得分:7)
_CaptureEq
被编写为旧式类的配方(如果你在基于散列的容器的Python 3上尝试它将无法正常工作),以及一个有你自己的Foo.__eq__
的配方定义明确声称这两个对象不相等时应该说“我不知道,如果我们是平等的,请问另一个对象”。
配方问题很容易解决:只需在比较包装类上定义__hash__
:
class _CaptureEq:
'Object wrapper that remembers "other" for successful equality tests.'
def __init__(self, obj):
self.obj = obj
self.match = obj
# If running on Python 3, this will be a new-style class, and
# new-style classes must delegate hash explicitly in order to populate
# the underlying special method slot correctly.
# On Python 2, it will be an old-style class, so the explicit delegation
# isn't needed (__getattr__ will cover it), but it also won't do any harm.
def __hash__(self):
return hash(self.obj)
def __eq__(self, other):
result = (self.obj == other)
if result:
self.match = other
return result
def __getattr__(self, name): # support anything else needed by __contains__
return getattr(self.obj, name)
您自己的__eq__
定义问题也很容易解决:在适当的时候返回NotImplemented
,这样您就不会声称为与未知对象的比较提供明确的答案:
class Foo(object):
def __init__(self, a, b, c):
self.a = a
self.b = b
self.c = c
def __key(self):
return (self.a, self.b, self.c)
def __eq__(self, other):
if not isinstance(other, Foo):
# Don't recognise "other", so let *it* decide if we're equal
return NotImplemented
return self.__key() == other.__key()
def __hash__(self):
return hash(self.__key())
通过这两个修复程序,您会发现Raymond的get_equivalent
食谱完全符合预期:
>>> from capture_eq import *
>>> bar_1 = Bar(1,2,3,4,5)
>>> bar_2 = Bar(1,2,3,10,11)
>>> summary = set((bar_1,))
>>> assert(bar_1 == bar_2)
>>> bar_equiv = get_equivalent(summary, bar_2)
>>> bar_equiv.d
4
>>> bar_equiv.e
5
更新:澄清只有正确处理Python 3案例时才需要显式__hash__
覆盖。
答案 1 :(得分:4)
问题在于set
比较两个对象“错误的方式”,以便拦截对__eq__()
的调用。从2006年开始的配方显然是针对容器编写的,当被问到是否存在x
时,容器会通过容器中已存在的候选y
值来执行:
x == y
比较,在这种情况下,__eq__()
上的x
可以在搜索过程中执行特殊操作。但是set
对象正在以相反的方式进行比较:
y == x
表示集合中的每个y。因此,当您的数据类型为set
时,此模式可能无法在此表单中使用。您可以通过这样设置Foo.__eq__()
来确认这一点:
def __eq__(self, other):
print '__eq__: I am', self.d, self.e, 'and he is', other.d, other.e
return self.__key() == other.__key()
然后您会看到如下消息:
__eq__: I am 4 5 and he is 10 11
确认相等比较对于已经在集合中的对象构成了相等的问题 - 唉,不用Hettinger的{{1}包裹的对象对象。
<强>更新强>
我忘了建议前进的方向:你有没有考虑过使用字典?既然你已经知道了一个键是对象内部数据的一个子集,你可能会发现从对象本身的想法中分离出键的概念可能会减少尝试这种复杂对象拦截的需要。 。只需编写一个新函数,给定一个对象和你的字典,计算密钥并在字典中查找并返回字典中已存在的对象(如果存在该密钥),否则将新对象插入密钥。
更新2:好吧,看看 - 尼克的答案在一个方向上使用_CaptureEq
强制 NotImplemented
进行比较在另一个方向。给那个家伙几个+ 1!
答案 2 :(得分:-2)
这里有两个问题。首先是:
t = _CaptureEq(item)
if t in container:
return t.match
return default
不按你的想法行事。特别是,t
永远不在container
,因为_CaptureEq
未定义__hash__
。这在Python 3中变得更加明显,因为它会指出这一点而不是提供默认的__hash__
。 _CaptureEq
的代码似乎相信提供__getattr__
将解决这个问题 - 它不会,因为Python的特殊方法查找不能保证完成与普通属性查找相同的步骤 - 这是同样的原因__hash__
(以及其他各种)需要在类上定义,并且不能在实例上进行monkeypatched。因此,最直接的方法是定义_CaptureEq.__hash__
,如下所示:
def __hash__(self):
return hash(self.obj)
但由于第二个问题,仍然无法保证工作:set
查找不能保证测试相等性。 set
基于哈希表,并且只有在哈希桶中有多个项目时才进行相等性测试。您不能(并且不希望)强制以不同方式散列到同一个存储桶中的项目,因为这是set
的所有实现细节。解决这个问题的最简单方法,就是整齐地回避第一个问题,就是改用列表:
summary = [bar_1]
assert(bar_1 == bar_2)
bar_equiv = get_equivalent(summary, bar_2)
assert(bar_equiv is bar_1)