Python运算符重载究竟是如何工作的?

时间:2017-01-11 21:09:51

标签: python python-2.7 class operator-overloading

我尝试了以下实验:

>>> 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 个答案:

答案 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;
}

有趣的部分是评论 - 它回答了你的问题:

  1. 如果两者都是相同的类型和新式类,它假定它可以做一个快捷方式:它会尝试丰富地比较它们。正常和反向返回NotImplemented并继续。

  2. 它进入try_rich_compare函数,在那里尝试再次比较它们,先是正常然后反转。

  3. 通过测试反向操作进行最后一次尝试:现在它将其反转,然后再次尝试正常(逆转操作)。

  4. (未显示)最后,如果对象相同a1 is a2返回观察到的False,则所有3种可能性都失败,然后进行最后一次测试。

  5. 如果您测试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