【问题标题】:Set "in" operator: uses equality or identity?设置“in”运算符:使用相等还是恒等?
【发布时间】:2012-02-01 01:10:59
【问题描述】:
class A(object):
    def __cmp__(self):
        print '__cmp__'
        return object.__cmp__(self)

    def __eq__(self, rhs):
        print '__eq__'
        return True
a1 = A()
a2 = A()
print a1 in set([a1])
print a1 in set([a2])

为什么第一行打印 True,而第二行打印 False?并且两者都没有进入运算符 eq

我正在使用 Python 2.6

【问题讨论】:

  • 集合可能在相等之前使用哈希码。尝试实现__hash__
  • 您是否尝试在注释掉 __cmp__ 函数的情况下运行此代码?
  • x.__contains__(y) <==> y in x
  • 只是为了确保您知道...该集合不是 in 运算符工作所必需的。如果你只需要一个简单的测试,[a1]中的a1就可以了。
  • 有趣的是...如果您删除该集合,则只有第二个([a2] 中的 a1)调用 eq。我猜 in 运算符会在优化时首先检查身份?

标签: python set identity operator-keyword equality


【解决方案1】:

Set __contains__ 按以下顺序进行检查:

 'Match' if hash(a) == hash(b) and (a is b or a==b) else 'No Match'

相关的 C 源代码在 Objects/setobject.c::set_lookkey() 和 Objects/object.c::PyObject_RichCompareBool() 中。

【讨论】:

  • List __contains__ 的行为方式似乎相同
【解决方案2】:

您还需要定义__hash__。例如

class A(object):
    def __hash__(self):
        print '__hash__'
        return 42

    def __cmp__(self, other):
        print '__cmp__'
        return object.__cmp__(self, other)

    def __eq__(self, rhs):
        print '__eq__'
        return True

a1 = A()
a2 = A()
print a1 in set([a1])
print a1 in set([a2])

会按预期工作。

作为一般规则,任何时候你实现__cmp__,你都应该实现一个__hash__,这样对于所有xy,这样x == yx.__hash__() == y.__hash__()

【讨论】:

  • 我的代码中实际上没有 cmp,只有 __eq__ (如果我删除 cmp 它仍然会产生 False)。我只是在寻找解释。所以你是说每次我实现 eq 我也需要 hash
  • 是的,完全正确。集合由哈希表支持,因此通过首先调用 x.__hash__() 执行集合成员资格测试 (x in my_set),检查 my_set 中是否有任何值散列到该值,然后仅在匹配项为时调​​用 __eq__找到了。
  • @GennadiyRozental 是的。 __hash__ 实现应该包含与__eq__ 实现相同的对象属性。 (只需将它们全部推入一个元组并 hash() 即可。)
  • 您的__cmp__ 似乎缺少other 参数。
【解决方案3】:

集合和字典通过使用散列作为完全相等检查的快速近似来提高速度。如果要重新定义相等,通常需要重新定义哈希算法,使其一致。

默认散列函数使用对象的标识,这对于完全相等的快速近似是非常无用的,但至少允许您使用任意类实例作为字典键并检索存储的值,如果您传递完全相同的对象作为键。但这意味着如果您重新定义相等性并且重新定义哈希函数,您的对象将进入字典/集合而不会抱怨不可哈希,但实际上仍不会按您期望的方式工作他们去。

更多详情请见the official python docs on __hash__

【讨论】:

  • +1 用于解释根本原因(默认哈希使用 id)。使解决方案易于遵循。
【解决方案4】:

一个切题的答案,但你的问题和我的测试让我很好奇。如果您忽略作为__hash__ 问题根源的集合运算符,那么您的问题仍然很有趣。

感谢我在this SO question 上获得的帮助,我能够通过源代码将 in 运算符追踪到它的根目录。在底部附近,我发现了 PyObject_RichCompareBool 函数,它在测试相等性之前确实测试了身份(参见关于“快速结果”的评论)。

因此,除非我误解了事情的运作方式,否则您问题的技术答案首先是身份,然后是平等,通过平等测试本身。重申一下,这不是您所看到行为的根源,而只是您问题的技术答案。

如果我误解了来源,请指正。

int
PyObject_RichCompareBool(PyObject *v, PyObject *w, int op)
{
    PyObject *res;
    int ok;

    /* Quick result when objects are the same.
       Guarantees that identity implies equality. */
    if (v == w) {
        if (op == Py_EQ)
            return 1;
        else if (op == Py_NE)
            return 0;
    }

    res = PyObject_RichCompare(v, w, op);
    if (res == NULL)
        return -1;
    if (PyBool_Check(res))
        ok = (res == Py_True);
    else
        ok = PyObject_IsTrue(res);
    Py_DECREF(res);
    return ok;
}

【讨论】:

    【解决方案5】:

    在比较相等之前,集合似乎使用哈希码,然后是标识。以下代码:

    class A(object):
        def __eq__(self, rhs):
            print '__eq__'
            return True
        def __hash__(self):
            print '__hash__'
            return 1
    
    a1 = A()
    a2 = A()
    
    print 'set1'
    set1 = set([a1])
    
    print 'set2'
    set2 = set([a2])
    
    print 'a1 in set1'
    print a1 in set1
    
    print 'a1 in set2'
    print a1 in set2
    

    输出:

    set1
    __hash__
    set2
    __hash__
    a1 in set1
    __hash__
    True
    a1 in set2
    __hash__
    __eq__
    True
    

    发生的事情似乎是:

    1. 在将元素插入散列时计算散列码。 (与现有元素进行比较。)
    2. 计算您使用 in 运算符检查的对象的哈希码。
    3. 检查集合中具有相同哈希码的元素,首先检查它们是否与您要查找的对象相同,或者它们在逻辑上是否相等。

    【讨论】:

      猜你喜欢
      • 2022-01-17
      • 1970-01-01
      • 2019-05-19
      • 1970-01-01
      • 2014-02-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多