4

我有一组对象,并且有兴趣从集合中获取特定对象。经过一番研究,我决定使用这里提供的解决方案: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__方法未被调用?希望这不是一个太晦涩难懂的问题。

4

3 回答 3

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

更新:阐明了__hash__仅需要显式覆盖才能正确处理 Python 3 案例。

于 2012-06-25T04:40:38.790 回答
4

问题在于,set比较两个对象的方式是“错误的”,以使这种模式拦截对__eq__(). 2006 年的配方显然是针对容器编写的,当被问及是否x存在时,会检查容器中已经存在的候选y值:

x == y

比较,在这种情况下__eq__()onx可以在搜索期间执行特殊操作。但是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 的对象包裹的_CaptureEq对象。

更新:

而且我忘了建议前进的方向:您是否考虑过使用字典?由于您在这里对作为对象内部数据子集的键有一个概念,您可能会发现将键的概念从对象本身的概念中分离出来可能会减轻尝试这种复杂的对象拦截的需要. 只需编写一个新函数,给定一个对象和您的字典,计算键并在字典中查找,如果键存在则返回字典中已经存在的对象,否则在键处插入新对象。

更新 2:好吧,看看那个——尼克的答案NotImplemented在一个方向上使用 a 来强制在另一个方向set上进行比较。给这个人几个+1!

于 2012-06-25T01:55:36.973 回答
-2

这里有两个问题。第一个是:

t = _CaptureEq(item)
if t in container:
    return t.match
return default

不做你想的。特别是,t永远不会in container,因为_CaptureEq没有定义__hash__. 这在 Python 3 中变得更加明显,因为它会向您指出这一点,而不是提供默认的__hash__. 的代码_CaptureEq似乎相信提供 an__getattr__将解决这个问题 - 它不会,因为 Python 的特殊方法查找不能保证通过与普通属性查找相同的所有步骤 - 这与__hash__(和其他各种)需要的相同原因被定义在一个类上,不能被猴子修补到一个实例上。因此,解决此问题的最直接方法是这样定义_CaptureEq.__hash__

def __hash__(self):
   return hash(self.obj)

但这仍然不能保证工作,因为第二个问题:set查找不能保证测试相等性。sets 基于哈希表,只有在哈希桶中有多个项目时才进行相等性测试。您不能(也不想)强制将散列不同的项目放入同一个存储桶中,因为这都是set. 解决这个问题的最简单方法,并且巧妙地避开第一个问题,是使用一个列表来代替:

summary = [bar_1]
assert(bar_1 == bar_2)
bar_equiv = get_equivalent(summary, bar_2)
assert(bar_equiv is bar_1)
于 2012-06-25T02:35:07.807 回答