内容简介:但反过来就不成立了,如果定义所以,绝大多数情况下,重写
起步
__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.c
的 object_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.c
的 do_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
数字很有说服力。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。