的行爲是唯一的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.__eq__(a1)
a1.__eq__(a1)
a1.__eq__(a1)
a1.__eq__(a1)
a1.__eq__(a1)
a1.__eq__(a1)
True
我不知道這種行爲是完全記錄,至少有一些提示中:
上次測試的存在可以,如果你測試a1 == a1
觀察__eq__
豐富的比較方法可能會返回單例NotImplemented,如果它不imp針對給定的一對參數進行操作。
和__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
函數進行比較以獲得相等性)。
很好的解釋。正如你注意到的,最後四次比較是由文檔定義的:首先嚐試從右側開始進行反向比較(如果RHS返回NotImplemented,可能會導致從左側嘗試),然後嘗試從左側開始普通比較(可能會如果左側返回NotImplemented,則導致從右側嘗試)。前兩個是優化。我認爲這一點很重要(儘管在文檔中沒有明確說明)是*比較函數被調用的確切次數是實現細節並且沒有定義行爲*。 – BrenBarn
@augurar我已將所有相關源代碼包含到問題中,並突出顯示了相關比較及其順序。如果我需要解釋其他任何東西,請告訴我:)(但是你不會得到流程圖xD) – MSeifert
也值得注意的是,如果一個對象沒有定義任何豐富的比較方法,那麼另一個對象的'__eq__'方法只會被調用一次,而不是兩次。 – augurar