Як обробляється __eq__ у Python і в якому порядку?


98

Оскільки 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__функції.

Я шукаю офіційне дерево рішень.

Відповіді:


119

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 та патч для уточнення. TLDR: підклас все-таки порівнюється першим, навіть якщо він на rhs.
більше

Привіт кев, приємний пост. Не могли б ви пояснити, де зафіксована перша точка маркера і чому вона розроблена саме так?
wim

1
Так, де це задокументовано для python 2? Це PEP?
Mr_and_Mrs_D

базуючись на цій відповіді та супровідних коментарях, це просто змусило мене розгубитися, ніж раніше.
Саджук

і до речі, чи визначити прив'язаний метод __eq__ лише на екземплярі якогось типу недостатньо, щоб == було замінено?
Саджук

5

Я пишу оновлену відповідь для Python 3 на це питання.

Як __eq__обробляється в Python і в якому порядку?

a == b

Загально зрозуміло, але не завжди випадок, який a == bвикликає a.__eq__(b), або type(a).__eq__(a, b).

Явно, порядок оцінки:

  1. якщо 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.

Звідки ми знаємо цей повний алгоритм?

Інші відповіді тут здаються неповними та застарілими, тому я збираюся оновити інформацію та показати вам, як ви могли б це шукати для себе.

Це вирішується на рівні С.

Тут нам потрібно розглянути два різні біти коду - за замовчуванням __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__метод із першого типу та викликати його. Поки результат не виконується, тобто реалізується, ми повертаємо його.

    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;

Висновок

У порівнянні спочатку ми поважаємо реалізацію підкласу порівняння.

Потім ми робимо спробу порівняння з реалізацією першого об’єкта, потім з другим, якщо його не викликали.

Нарешті, ми використовуємо тест на ідентичність для порівняння на рівність.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.