我尝试了以下实验:

>>> class A(object):
...     def __init__(self, name):
...         self.name = name
...     def __str__(self):
...         return self.name
...     def __eq__(self, other):
...         print('{}.__eq__({})'.format(self, other))
...         return NotImplemented
...
>>> a1 = A('a1')
>>> a2 = A('a2')
>>> a1 == a2
a1.__eq__(a2)
a2.__eq__(a1)
a1.__eq__(a2)
a2.__eq__(a1)
a2.__eq__(a1)
a1.__eq__(a2)
False


这里到底发生了什么?

更一般而言,是否存在评估操作员时发生的确切流程的官方文档? data model文档暗示了某种后备行为,但未精确描述其含义。有几种使情况复杂化的可能性:


ab可以是相同或不同的类型
ab可能会或可能不会定义__eq__方法
a.__eq__(b)b.__eq__(a)可能返回NotImplemented或其他值。


某种流程图会有所帮助。



编辑:在将此问题标记为重复之前,请确保假定的重复回答以下问题:


为什么__eq__在给定模式下被调用6次?
该行为在何处得到充分记录?
我可以得到流程图吗?

最佳答案

该行为仅是python-2.x,并且是内部比较工作的方式(至少是CPython)的一部分,但前提是两者都是新样式类且两个参数都具有相同的类型!

源C代码读取(我突出显示了完成和/或跳过比较的部分):

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

    assert(Py_LT <= op && op <= Py_GE);
    if (Py_EnterRecursiveCall(" in cmp"))
        return NULL;

    /* If the types are equal, and not old-style instances, try to
       get out cheap (don't bother with coercions etc.). */
    if (v->ob_type == w->ob_type && !PyInstance_Check(v)) {
        cmpfunc fcmp;
        richcmpfunc frich = RICHCOMPARE(v->ob_type);
        /* If the type has richcmp, try it first.  try_rich_compare
           tries it two-sided, which is not needed since we've a
           single type only. */
        if (frich != NULL) {
            /****************************************************/
            /* 1. This first tries v.__eq__(w) then w.__eq__(v) */
            /****************************************************/
            res = (*frich)(v, w, op);
            if (res != Py_NotImplemented)
                goto Done;
            Py_DECREF(res);
        }
        /* No richcmp, or this particular richmp not implemented.
           Try 3-way cmp. */
        fcmp = v->ob_type->tp_compare;
        if (fcmp != NULL)
            /***********************************************/
            /* Skipped because you don't implement __cmp__ */
            /***********************************************/
            int c = (*fcmp)(v, w);
            c = adjust_tp_compare(c);
            if (c == -2) {
                res = NULL;
                goto Done;
            }
            res = convert_3way_to_object(op, c);
            goto Done;
        }
    }

    /* Fast path not taken, or couldn't deliver a useful result. */
    res = do_richcmp(v, w, op);
Done:
    Py_LeaveRecursiveCall();
    return res;
}

/* Try a genuine rich comparison, returning an object.  Return:
   NULL for exception;
   NotImplemented if this particular rich comparison is not implemented or
     undefined;
   some object not equal to NotImplemented if it is implemented
     (this latter object may not be a Boolean).
*/
static PyObject *
try_rich_compare(PyObject *v, PyObject *w, int op)
{
    richcmpfunc f;
    PyObject *res;

    if (v->ob_type != w->ob_type &&
        PyType_IsSubtype(w->ob_type, v->ob_type) &&
        (f = RICHCOMPARE(w->ob_type)) != NULL) {
            /*******************************************************************************/
            /* Skipped because you don't compare unequal classes where w is a subtype of v */
            /*******************************************************************************/
        res = (*f)(w, v, _Py_SwappedOp[op]);
        if (res != Py_NotImplemented)
            return res;
        Py_DECREF(res);
    }
    if ((f = RICHCOMPARE(v->ob_type)) != NULL) {
            /*****************************************************************/
            /** 2. This again tries to evaluate v.__eq__(w) then w.__eq__(v) */
            /*****************************************************************/
        res = (*f)(v, w, op);
        if (res != Py_NotImplemented)
            return res;
        Py_DECREF(res);
    }
    if ((f = RICHCOMPARE(w->ob_type)) != NULL) {
            /***********************************************************************/
            /* 3. This tries the reversed comparison: w.__eq__(v) then v.__eq__(w) */
            /***********************************************************************/
        return (*f)(w, v, _Py_SwappedOp[op]);
    }
    res = Py_NotImplemented;
    Py_INCREF(res);
    return res;
}


有趣的部分是评论-可以回答您的问题:


如果两者都是相同的类型和新样式的类,则假定它可以做一个捷径:它尝试进行丰富的比较。正常返回和反向返回NotImplemented并继续执行。
它进入try_rich_compare函数,在那里尝试再次比较它们,首先是正常值,然后是相反值。
通过测试反向操作进行最后一次尝试:现在,它比较反向操作,然后再次尝试正常(反向操作的反向操作)。
(未显示)最后,所有3种可能性均失败,然后如果对象相同的a1 is a2进行最后测试,则返回观察到的False


如果测试a1 == a1,则可以观察到最后一个测试的存在:

>>> a1 == a1
a1.__eq__(a1)
a1.__eq__(a1)
a1.__eq__(a1)
a1.__eq__(a1)
a1.__eq__(a1)
a1.__eq__(a1)
True




我不知道该行为是否已得到充分记录,至少在__eq__的文档中有一些提示


  如果富比较方法未实现给定参数对的操作,则它可能返回单例NotImplemented。


__cmp__


  如果未定义丰富比较(请参见上文),则由比较操作调用。




其他一些观察:

请注意,如果您定义__cmp__,则它不像return NotImplemented那样尊重__eq__(因为它在PyObject_RichCompare中输入了先前跳过的分支):

class A(object):
    def __init__(self, name):
        self.name = name
    def __str__(self):
        return self.name
    def __eq__(self, other):
        print('{}.__eq__({})'.format(self, other))
        return NotImplemented
    def __cmp__(self, other):
        print('{}.__cmp__({})'.format(self, other))
        return NotImplemented


>>> a1, a2 = A('a1'), A('a2')
>>> a1 == a2
a1.__eq__(a2)
a2.__eq__(a1)
a1.__cmp__(a2)
a2.__cmp__(a1)
False


如果您显式地与超类和继承的类进行比较,则可以轻松看出子类或相同类的行为:

>>> class A(object):
...     def __init__(self, name):
...         self.name = name
...     def __str__(self):
...         return self.name
...     def __eq__(self, other):
...         print('{}.__eq__({}) from A'.format(self, other))
...         return NotImplemented
...
>>>
>>> class B(A):
...     def __eq__(self, other):
...         print('{}.__eq__({}) from B'.format(self, other))
...         return NotImplemented
...
>>>
>>> a1, a2 = A('a1'), B('a2')
>>> a1 == a2
a2.__eq__(a1) from B
a1.__eq__(a2) from A
a1.__eq__(a2) from A
a2.__eq__(a1) from B
a2.__eq__(a1) from B
a1.__eq__(a2) from A
False
>>> a2 == a1
a2.__eq__(a1) from B
a1.__eq__(a2) from A
a1.__eq__(a2) from A
a2.__eq__(a1) from B
False




最后的评论:

我添加了用于“打印”的代码,它在gist中进行比较。如果您知道如何创建python-c-extensions,则可以自己编译和运行代码(需要使用两个参数调用myrichcmp函数以进行相等性比较)。

10-08 07:14