2017-01-11 44 views
2

我嘗試了以下實驗:Python運算符重載究竟如何工作?

>>> 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倍?
  • 行爲完全記錄在哪裏?
  • 我可以得到一個流程圖嗎?

回答

2

的行爲是唯一的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; 
} 

有趣的部分是註釋 - 這回答了你的問題:

  1. 如果兩者都是相同的類型和新風格的類,它假定它可以做一個快捷方式:它試圖豐富地比較它們。正常和逆轉回報NotImplemented並繼續。

  2. 它進入try_rich_compare功能,它試圖再次比較它們,首先正常然後顛倒。

  3. 最後一次嘗試是通過測試反向操作來完成的:現在,將它進行比較,然後再次嘗試正常(反向操作的反轉)。

  4. (未顯示)最後所有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函數進行比較以獲得相等性)。

+2

很好的解釋。正如你注意到的,最後四次比較是由文檔定義的:首先嚐試從右側開始進行反向比較(如果RHS返回NotImplemented,可能會導致從左側嘗試),然後嘗試從左側開始普通比較(可能會如果左側返回NotImplemented,則導致從右側嘗試)。前兩個是優化。我認爲這一點很重要(儘管在文檔中沒有明確說明)是*比較函數被調用的確切次數是實現細節並且沒有定義行爲*。 – BrenBarn

+0

@augurar我已將所有相關源代碼包含到問題中,並突出顯示了相關比較及其順序。如果我需要解釋其他任何東西,請告訴我:)(但是你不會得到流程圖xD) – MSeifert

+0

也值得注意的是,如果一個對象沒有定義任何豐富的比較方法,那麼另一個對象的'__eq__'方法只會被調用一次,而不是兩次。 – augurar

-3

請閱讀Python的手冊約NotImplemented:「數字方法和豐富的比較方法應該返回這個值,如果他們不執行所提供的操作數的操作(然後解釋器將嘗試反射操作,或者一些其他的後備。 ,這取決於操作員。)其真值是真實的。「

,如果你改變返回的回報self.name == other.name NotImplemented也就是說,將是EQ單個呼叫

+0

這不回答這個問題。我的問題是:爲什麼觀察到的行爲會發生? – augurar

+0

我已經寫道:因爲你從你的代碼中返回了NotImplemented,所以「解釋器會根據操作符嘗試反射操作或其他一些回退操作。」爲什麼6次?因爲你使用Python 2.7。 Python 3.5.2和3.6.0只嘗試了兩種變體的比較。可能它是Python 2.7中的一個錯誤。 – Andrew