我尝试了以下实验:
>>> 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文档提到某种回退行为,但没有准确描述它是什么。有几种可能使情况复杂化:
a
和b
可以是相同或不同的类型a
和b
可能会也可能不会定义__eq__
方法a.__eq__(b)
或b.__eq__(a)
可能会返回NotImplemented
或其他值。某种流程图会有所帮助。
修改:在将此问题标记为重复之前,请确保所谓的副本回答以下内容:
__eq__
在给定的模式中被调用了6次?答案 0 :(得分:2)
行为只是python-2.x,它是丰富比较内部工作方式的一部分(至少是CPython),但前提是两者都是新式类,两个参数都有相同的类型! 强>
源代码读取(我突出显示了完成和/或跳过比较的部分):
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
函数,在那里尝试再次比较它们,先是正常然后反转。
通过测试反向操作进行最后一次尝试:现在它将其反转,然后再次尝试正常(逆转操作)。
(未显示)最后,如果对象相同a1 is a2
返回观察到的False
,则所有3种可能性都失败,然后进行最后一次测试。
如果您测试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
最后评论:
我添加了以前的代码&#34; print&#34;它在gist中进行比较的地方。如果你知道如何创建python-c-extensions,你可以自己编译和运行代码(需要使用两个参数调用myrichcmp
函数来比较相等性。)
答案 1 :(得分:-3)
请阅读关于NotImplemented的Python手册:“如果没有为所提供的操作数实现操作,则数值方法和丰富的比较方法应返回此值。(解释器将尝试反射操作,或其他一些后退,具体取决于运算符。)它的真值是真的。“
即如果您通过返回self.name == other.name更改返回NotImplemented,则只会调用 eq