__eq__ 中包含着隐藏关系

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

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

起步

__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

数字很有说服力。


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

查看所有标签

猜你喜欢:

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

Code Reading

Code Reading

Diomidis Spinellis / Addison-Wesley Professional / 2003-06-06 / USD 64.99

This book is a unique and essential reference that focuses upon the reading and comprehension of existing software code. While code reading is an important task faced by the vast majority of students,......一起来看看 《Code Reading》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具