__eq__ 中包含着隐藏关系

栏目: Python · 发布时间: 6年前

内容简介:但反过来就不成立了,如果定义所以,绝大多数情况下,重写

起步

__eq__ 是用来重载操作符 == 的;相对的, __ne__ 用来重载 != 。这两个魔术方法存在一个容易忽略的隐藏关系,就是当类中定义了 __eq__ 而没有定义 __ne__ 的时候,对于 != 操作会取 __ne__() 的反向结果。

但反过来就不成立了,如果定义 __ne__ 而没有定义 __eq__ ,对于 == 操作则会进行默认的行为。也就是说 x != y 不一定就是 x == y 的相反结果。它们之间并不存在这样的隐藏调用的关系。

所以,绝大多数情况下,重写 __eq__ 方法就够了。

其他比较操作符:

object.__lt__(self, other)
object.__le__(self, other)
object.__gt__(self, other)
object.__ge__(self, other)

也都不存在这个关系。没定义的操作符会抛出 NotImplemented 异常。

引发的思考

python 中所有类都继承自 object ,而 object.__ne__ 是存在的:

>>> class A:
...     def __eq__(self, other):
...         return True
...
>>> id(object.__ne__)
2024343946008
>>> id(A.__ne__)
2024343946008
>>> a = object()
>>> b = object()
>>> a == b
False
>>> a != b
True
>>> a = A()
>>> b = A()
>>> a == b
True
>>> a != b
False
>>>

这里的问题是, A.__ne__ 从基类继承过来,它明明就存在的啊,为什么这边没有去调用而是通过 A.__eq__ 去判断呢,很奇怪是不是?

这要探究就得深入CPython中去了,执行 __ne__ 是在 typeobject.cobject_richcompare 中:

static PyObject *
object_richcompare(PyObject *self, PyObject *other, int op)
{
    PyObject *res;

    switch (op) {

    case Py_EQ:
        res = (self == other) ? Py_True : Py_NotImplemented;
        Py_INCREF(res);
        break;

    case Py_NE:
        /* By default, __ne__() delegates to __eq__() and inverts the result,
           unless the latter returns NotImplemented. */
        // 上面的注释是,__ne__ 委托给 __eq__ 来执行,对结果取反
        if (self->ob_type->tp_richcompare == NULL) {
            res = Py_NotImplemented;
            Py_INCREF(res);
            break;
        }
        res = (*self->ob_type->tp_richcompare)(self, other, Py_EQ);
        if (res != NULL && res != Py_NotImplemented) {
            int ok = PyObject_IsTrue(res);
            Py_DECREF(res);
            if (ok < 0)
                res = NULL;
            else {
                if (ok)
                    res = Py_False;
                else
                    res = Py_True;
                Py_INCREF(res);
            }
        }
        break;
    }

    return res;
}

这里注释写得很清楚了,重点在于 res = (*self->ob_type->tp_richcompare)(self, other, Py_EQ); 中执行了 PyObject_RichCompare ,这是一种性能比较低的比较方式,它实际上使用默认的逻辑 __eq__ ,这部分在 object.cdo_richcompare 函数中。

static PyObject *
do_richcompare(PyObject *v, PyObject *w, int op)
{
    richcmpfunc f;
    PyObject *res;
    int checked_reverse_op = 0;

    if (v->ob_type != w->ob_type &&
        PyType_IsSubtype(w->ob_type, v->ob_type) &&
        (f = w->ob_type->tp_richcompare) != NULL) {
        checked_reverse_op = 1;
        res = (*f)(w, v, _Py_SwappedOp[op]);
        if (res != Py_NotImplemented)
            return res;
        Py_DECREF(res);
    }
    if ((f = v->ob_type->tp_richcompare) != NULL) {
        res = (*f)(v, w, op);
        if (res != Py_NotImplemented)
            return res;
        Py_DECREF(res);
    }
    if (!checked_reverse_op && (f = w->ob_type->tp_richcompare) != NULL) {
        res = (*f)(w, v, _Py_SwappedOp[op]);
        if (res != Py_NotImplemented)
            return res;
        Py_DECREF(res);
    }
    /* If neither object implements it, provide a sensible default
       for == and !=, but raise an exception for ordering. */
    switch (op) {
    case Py_EQ:
        res = (v == w) ? Py_True : Py_False;
        break;
    case Py_NE:
        res = (v != w) ? Py_True : Py_False;
        break;
    default:
        PyErr_Format(PyExc_TypeError,
                     "'%s' not supported between instances of '%.100s' and '%.100s'",
                     opstrings[op],
                     v->ob_type->tp_name,
                     w->ob_type->tp_name);
        return NULL;
    }
    Py_INCREF(res);
    return res;
}

不管在 PyObject_RichCompare 还是在 do_richcompare 我都看到了 if (res != Py_NotImplemented) 这样的语句。难道我们可以在比较操作中返回 NotImplemented ?我不禁要试一下:

class A:
    def __eq__(self, other):
        return NotImplemented
    def __ne__(self, other):
        return NotImplemented

a = A()
b = A()
print(a == b) # True
print(a != b) # True
print(a != a) # False

显然这里的 __ne__ 被实现,不会去调用 __eq__ 了,而我们返回了 NotImplemented ,也就是告诉解释器这些方法不存在,这就让 do_richcompare 中的 3 个 if 都不成立,乖乖走后续的 switch 块,这部分就直接通过指针是否相等来判断了。

性能比较

通过 PyObject_RichCompare 来比较效率会比较低,甚至有的需要处理 NotImplemented 的情况。我们对这几种情况做个对比:

class Default:
    pass

class NeOnly:
    def __ne__(self, other):
        return not self == other

class RNotImplemented:
    def __ne__(self, other):
        return NotImplemented

def c_level():
    cl = Default()
    return lambda: cl != cl

def high_level_python():
    hlp = NeOnly()
    return lambda: hlp != hlp

def low_level_python():
    llp = RNotImplemented()
    return lambda: llp != llp

import timeit
print(min(timeit.repeat(c_level())))          # 0.10529670296476416
print(min(timeit.repeat(high_level_python())))# 0.31792451405355615
print(min(timeit.repeat(low_level_python()))) # 0.3742690036398786

数字很有说服力。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

程序员的数学

程序员的数学

结城浩 / 管杰 / 人民邮电出版社 / 2012-10 / 49.00元

如果数学不好,是否可以成为一名程序员呢?答案是肯定的。 本书最适合:数学糟糕但又想学习编程的你。 没有晦涩的公式,只有好玩的数学题。 帮你掌握编程所需的“数学思维”。 日文版已重印14次! 编程的基础是计算机科学,而计算机科学的基础是数学。因此,学习数学有助于巩固编程的基础,写出更健壮的程序。 本书面向程序员介绍了编程中常用的数学知识,借以培养初级程序员的数学思维。读......一起来看看 《程序员的数学》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

URL 编码/解码
URL 编码/解码

URL 编码/解码