Как __eq__ обрабатывается в Python и в каком порядке?

106

Поскольку 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__функции.

Я ищу официальное дерево решений.

PyProg
источник

Ответы:

126

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 ?
Нед Батчелдер
источник
69

Когда 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), который является Trueiff aи bявляется одним и тем же объектом.

Если какой-либо из специальных методов вернет NotImplemented , Python действует так, как будто метода не существует.

Обратите внимание на последний шаг: если ни один из них aне bперегружается ==, то a == bэто то же самое, что и a is b.


С https://eev.ee/blog/2012/03/24/python-faq-equality/

кев
источник
1
Ух, кажется, документы Python 3 были неверными. См. Bugs.python.org/issue4395 и исправление для уточнения. TL; DR: подкласс все еще сравнивается первым, даже если он находится справа.
максимум
Привет, Кев, хороший пост. Не могли бы вы объяснить, где задокументирован первый пункт и почему он так разработан?
wim
1
Да, где это описано для Python 2? Это PEP?
Mr_and_Mrs_D
Основываясь на этом ответе и сопровождающих его комментариях, это просто сбило меня с толку, чем раньше.
Sajuuk
и, кстати, определить связанный метод __eq__ только для экземпляра некоторого типа, которого недостаточно для переопределения ==?
Sajuuk
11

Я пишу обновленный ответ на этот вопрос для Python 3.

Как __eq__обрабатывается в Python и в каком порядке?

a == b

Обычно понимается, но не всегда, когда a == bвызывается a.__eq__(b), или type(a).__eq__(a, b).

Явно порядок оценки следующий:

  1. if bтип является строгим подклассом (не того же типа) aтипа и имеет __eq__, вызовите его и верните значение, если сравнение реализовано,
  2. иначе, если aесть __eq__, вызовите его и верните, если сравнение реализовано,
  3. иначе, посмотрите, не вызвали ли мы b, __eq__и он есть, затем вызовите и верните его, если сравнение реализовано,
  4. иначе, наконец, сделайте сравнение на идентичность, такое же сравнение, как 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_richcomparefor 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;

Вывод

При сравнении мы в первую очередь уважаем реализацию подкласса сравнения.

Затем мы пытаемся сравнить с реализацией первого объекта, а затем со вторым, если он не был вызван.

Наконец, мы используем тест на идентичность для сравнения на равенство.

Аарон Холл
источник