Skip to main content
 首页 » 编程设计

python之Python 运算符重载到底是如何工作的

2025年05月04日13mfrbuaa

我尝试了以下实验:

>>> 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 次?
  • 行为的完整记录在哪里?
  • 我能得到流程图吗?

请您参考如下方法:

该行为仅适用于 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__ 的文档中有一些提示。

A rich comparison method may return the singleton NotImplemented if it does not implement the operation for a given pair of arguments.

和__cmp__ :

Called by comparison operations if rich comparison (see above) is not defined.


更多观察:

请注意,如果您定义 __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 

如果您明确地与父类(super class)和继承类进行比较,则可以很容易地看出子类或相同类的行为:

>>> 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 函数来比较是否相等)。