Я пишу оновлену відповідь для Python 3 на це питання.
Як __eq__
обробляється в Python і в якому порядку?
a == b
Загально зрозуміло, але не завжди випадок, який a == b
викликає a.__eq__(b)
, або type(a).__eq__(a, b)
.
Явно, порядок оцінки:
- якщо
b
тип 'є суворим підкласом (не однотипним) типу a
' і має __eq__
, викликайте його та повертайте значення, якщо здійснено порівняння,
- в іншому випадку, якщо
a
є __eq__
, зателефонуйте йому та поверніть його, якщо порівняння здійснено,
- в іншому випадку подивіться, чи ми не викликали b,
__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
.
Звідки ми знаємо цей повний алгоритм?
Інші відповіді тут здаються неповними та застарілими, тому я збираюся оновити інформацію та показати вам, як ви могли б це шукати для себе.
Це вирішується на рівні С.
Тут нам потрібно розглянути два різні біти коду - за замовчуванням __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;
Висновок
У порівнянні спочатку ми поважаємо реалізацію підкласу порівняння.
Потім ми робимо спробу порівняння з реалізацією першого об’єкта, потім з другим, якщо його не викликали.
Нарешті, ми використовуємо тест на ідентичність для порівняння на рівність.