Я пишу оновлену відповідь для 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;
Висновок
У порівнянні спочатку ми поважаємо реалізацію підкласу порівняння.
Потім ми робимо спробу порівняння з реалізацією першого об’єкта, потім з другим, якщо його не викликали.
Нарешті, ми використовуємо тест на ідентичність для порівняння на рівність.