Каков порядок выполнения __eq__, если одна сторона наследует от другой?

Недавно я наткнулся на странное поведение, касающееся порядка __eq__ методов __eq__, если одна сторона сравнения является объектом, который наследуется от другой.

Я попробовал это в Python 3.7.2 в общеизвестном академическом примере. Обычно, учитывая сравнение на равенство a == b, я ожидаю, a.__eq__ будет вызван a.__eq__, а затем b.__eq__, если первый вызов вернул NotImplemented. Однако, похоже, что это не так, если a и b являются частью одной и той же иерархии классов. Рассмотрим следующий пример:

class A(object):
  def __eq__(self, other):
    print("Calling A({}).__eq__".format(self))
    return NotImplemented

class B(A):
  def __eq__(self, other):
    print("Calling B({}).__eq__".format(self))
    return True

class C(object):
  def __eq__(self, other):
    print("Calling C({}).__eq__".format(self))
    return False

a = A()
b = B()
c = C()

print("a: {}".format(a)) # output "a: <__main__.A object at 0x7f8fda95f860>"
print("b: {}".format(b)) # output "b: <__main__.B object at 0x7f8fda8bcfd0>"
print("c: {}".format(c)) # output "c: <__main__.C object at 0x7f8fda8bcef0>"

a == a # case 1

a == b # case 2.1
b == a # case 2.2

a == c # case 3.1
c == a # case 3.2

В случае 1 я ожидаю, a.__eq__ будет вызван дважды, и это также то, что я получаю:

Calling A(<__main__.A object at 0x7f8fda95f860>).__eq__
Calling A(<__main__.A object at 0x7f8fda95f860>).__eq__

Однако в случаях 2.1 и 2.2 b.__eq__ всегда выполняется первым, независимо от того, на какой стороне сравнения он стоит:

Calling B(<__main__.B object at 0x7f8fda8bcfd0>).__eq__ # case 2.1
Calling B(<__main__.B object at 0x7f8fda8bcfd0>).__eq__ # case 2.2

В случаях 3.1 и 3.2 левая часть снова оценивается первой, как я и ожидал:

Calling A(<__main__.A object at 0x7f8fda95f860>).__eq__ # case 3.1
Calling C(<__main__.C object at 0x7f8fda8bcef0>).__eq__ # case 3.1
Calling C(<__main__.C object at 0x7f8fda8bcef0>).__eq__ # case 3.2

Кажется, что, если сравниваемые объекты связаны друг с другом, __eq__ объекта дочернего класса всегда вычисляется первым. Есть ли более глубокие причины этого поведения? Если так, это где-то задокументировано? PEP 207 не упоминает этот случай, насколько я вижу. Или я что-то упускаю здесь очевидное?

Ответы

Ответ 1

Из официальной документации за __eq__:

Если операнды имеют разные типы, и тип правого операнда является прямым или косвенным подклассом типа левого операнда, отраженный метод правого операнда имеет приоритет, в противном случае метод левого операнда имеет приоритет.