Как __eq__ обрабатывается в Python и в каком порядке?
Поскольку Python не предоставляет левую / правую версии своих операторов сравнения, как он решает, какую функцию вызывать?
class A(object):
def __eq__(self, other):
print "A __eq__ called"
return self.value == other
class B(object):
def __eq__(self, other):
print "B __eq__ called"
return self.value == other
>>> a = A()
>>> a.value = 3
>>> b = B()
>>> b.value = 4
>>> a == b
"A __eq__ called"
"B __eq__ called"
False
Кажется, это __eq__
функции. Просто ищу официальное дерево решений.
2 ответа
a == b
выражение вызывает A.__eq__
, поскольку он существует. Его код включает в себя self.value == other
, Поскольку int не умеют сравнивать себя с B, Python пытается вызвать B.__eq__
чтобы увидеть, если он знает, как сравнить себя с Int.
Если вы измените свой код, чтобы показать, какие значения сравниваются:
class A(object):
def __eq__(self, other):
print("A __eq__ called: %r == %r ?" % (self, other))
return self.value == other
class B(object):
def __eq__(self, other):
print("B __eq__ called: %r == %r ?" % (self, other))
return self.value == other
a = A()
a.value = 3
b = B()
b.value = 4
a == b
он напечатает:
A __eq__ called: <__main__.A object at 0x013BA070> == <__main__.B object at 0x013BA090> ?
B __eq__ called: <__main__.B object at 0x013BA090> == 3 ?
Когда Python2.x видит a == b
, он пытается следующее.
- Если
type(b)
класс нового стиля, иtype(b)
это подклассtype(a)
, а такжеtype(b)
переопределено__eq__
то результатb.__eq__(a)
, - Если
type(a)
переопределено__eq__
(то есть,type(a).__eq__
неobject.__eq__
), то результатa.__eq__(b)
, - Если
type(b)
переопределено__eq__
то результатb.__eq__(a)
, - Если ничего из вышеперечисленного не так, Python повторяет процесс поиска
__cmp__
, Если он существует, объекты равны, если он возвращаетzero
, - В качестве последнего отступления Python вызывает
object.__eq__(a, b)
, которыйTrue
тогда и только тогдаa
а такжеb
это один и тот же объект.
Если какой-либо из специальных методов возвращает NotImplemented
, Python действует так, как будто метод не существует.
Обратите внимание на последний шаг: если ни a
ни b
перегрузки ==
, затем a == b
такой же как a is b
,
Я пишу обновленный ответ на этот вопрос для Python 3.
Как
__eq__
обрабатывается на Python и в каком порядке?a == b
Обычно понимают, но не всегда, что a == b
призывает a.__eq__(b)
, или type(a).__eq__(a, b)
.
Явно порядок оценки следующий:
- если
b
тип является строгим подклассом (не того же типа) классаa
типа и имеет__eq__
, вызовите его и верните значение, если сравнение реализовано, - иначе, если
a
имеет__eq__
, вызовите его и верните, если сравнение реализовано, - иначе, посмотрим, не называли ли мы b's
__eq__
и он есть, затем вызовите и верните его, если сравнение реализовано, - иначе, наконец, сделайте сравнение на идентичность, такое же сравнение, как
is
.
Мы знаем, не реализовано ли сравнение, если метод возвращает NotImplemented
.
(В Python 2 был __cmp__
метод, который искали, но он устарел и был удален в Python 3.)
Давайте проверим поведение первой проверки для себя, допустив B подкласс A, который показывает, что принятый ответ неверен в этом отношении:
class A:
value = 3
def __eq__(self, other):
print('A __eq__ called')
return self.value == other.value
class B(A):
value = 4
def __eq__(self, other):
print('B __eq__ called')
return self.value == other.value
a, b = A(), B()
a == b
который только печатает B __eq__ called
перед возвращением False
.
Откуда нам знать этот полный алгоритм?
Другие ответы здесь кажутся неполными и устаревшими, поэтому я собираюсь обновить информацию и показать вам, как вы можете найти это самостоятельно.
Это обрабатывается на уровне C.
Здесь нам нужно рассмотреть два разных фрагмента кода - значение по умолчанию __eq__
для объектов класса object
, и код, который ищет и вызывает __eq__
независимо от того, использует ли он значение по умолчанию __eq__
или индивидуальный.
По умолчанию __eq__
Ищу __eq__
в соответствующей документации C api показывает нам, что__eq__
обрабатывается tp_richcompare
- который в "object"
определение типа в cpython/Objects/typeobject.c
определяется в object_richcompare
за case Py_EQ:
.
case Py_EQ:
/* Return NotImplemented instead of False, so if two
objects are compared, both get a chance at the
comparison. See issue #1393. */
res = (self == other) ? Py_True : Py_NotImplemented;
Py_INCREF(res);
break;
Так вот, если self == other
мы возвращаемся True
, иначе мы вернем NotImplemented
объект. Это поведение по умолчанию для любого подкласса объекта, который не реализует свой собственный__eq__
метод.
Как __eq__
называется
Затем мы находим документы C API, функцию PyObject_RichCompare, которая вызываетdo_richcompare
.
Тогда мы видим, что tp_richcompare
функция, созданная для "object"
Определение C называется do_richcompare
, так что давайте посмотрим на это более внимательно.
Первая проверка в этой функции - условия сравниваемых объектов:
- являются не тот же тип, но
- второй тип является подклассом первого типа, и
- второй тип имеет
__eq__
метод
затем вызовите другой метод с замененными аргументами, возвращая значение, если оно реализовано. Если этот метод не реализован, продолжаем...
if (!Py_IS_TYPE(v, Py_TYPE(w)) &&
PyType_IsSubtype(Py_TYPE(w), Py_TYPE(v)) &&
(f = Py_TYPE(w)->tp_richcompare) != NULL) {
checked_reverse_op = 1;
res = (*f)(w, v, _Py_SwappedOp[op]);
if (res != Py_NotImplemented)
return res;
Py_DECREF(res);
Затем мы посмотрим, сможем ли мы найти __eq__
метод из первого типа и вызовите его. Пока результат NotImplemented, то есть реализован, мы его возвращаем.
if ((f = Py_TYPE(v)->tp_richcompare) != NULL) {
res = (*f)(v, w, op);
if (res != Py_NotImplemented)
return res;
Py_DECREF(res);
Иначе, если мы не пробовали метод другого типа, а он есть, мы затем пробуем его, и если сравнение реализовано, мы возвращаем его.
if (!checked_reverse_op && (f = Py_TYPE(w)->tp_richcompare) != NULL) {
res = (*f)(w, v, _Py_SwappedOp[op]);
if (res != Py_NotImplemented)
return res;
Py_DECREF(res);
}
Наконец, мы получаем запасной вариант, если он не реализован ни для одного из типов.
Откат проверяет идентичность объекта, то есть является ли он тем же объектом в том же месте памяти - это та же проверка, что и для self is other
:
/* 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;
Вывод
При сравнении мы в первую очередь уважаем реализацию подкласса сравнения.
Затем мы пытаемся сравнить с реализацией первого объекта, а затем со вторым, если он не был вызван.
Наконец, мы используем тест на идентичность для сравнения на равенство.