Python - __eq__方法未被调用

时间:2012-06-25 01:41:44

标签: python

我有一组对象,并且对从集合中获取特定对象感兴趣。经过一些研究,我决定使用此处提供的解决方案: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__方法?希望这不是一个问题的模糊。

3 个答案:

答案 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)