簡體   English   中英

Python運算符重載到底如何工作?

[英]How exactly does Python operator overloading work?

我嘗試了以下實驗:

>>> 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

這里到底發生了什么?

更一般而言,是否存在評估操作員時發生的確切流程的官方文檔? 數據模型文檔暗示了某種后備行為,但沒有精確描述它是什么。 有幾種使情況復雜化的可能性:

  • 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;
}

有趣的部分是評論-可以回答您的問題:

  1. 如果兩者都是相同的類型和新樣式的類,則假定它可以做一個捷徑:它嘗試進行豐富的比較。 正常返回和反向返回NotImplemented並繼續執行。

  2. 它進入try_rich_compare函數,在那里嘗試再次比較它們,首先是正常值,然后是相反的值。

  3. 通過測試反向操作進行最后一次嘗試:現在,它比較反向操作,然后再次嘗試正常(反向操作的反向操作)。

  4. (未顯示)最后,所有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__它將不像__eq__那樣尊重return NotImplemented (因為它在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

最后的評論:

我添加了我用來“打印” 要點的代碼 如果您知道如何創建python-c-extensions,則可以自己編譯並運行代碼(需要使用兩個參數調用myrichcmp函數以進行相等性比較)。

請閱讀有關NotImplemented的Python手冊:“如果數字方法和豐富比較方法未實現所提供的操作數的操作,則應返回此值。(然后,解釋程序將嘗試使用反射操作或其他后備方式,具體取決於運算符。 )其真實價值是真實的。”

也就是說,如果您通過return self.name == other.name更改return NotImplemented,將只有一個對eq的調用

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM