Ответ 1
Это проблема # 6970 в трекере Python; он остается незафиксированным в версиях 2.7 и Python 3.0 и 3.1.
Это вызвано двумя местами, которые пытаются как прямое, так и замененное сравнение, когда выполняется сравнение между двумя пользовательскими классами с методами __eq__
.
Богатые сравнения проходят через PyObject_RichCompare()
функцию, которая для объектов с разными типами (косвенно) делегирует на try_rich_compare()
. В этой функции v
и w
находятся объекты левого и правого операндов, и поскольку оба имеют метод __eq__
, функция вызывает как v->ob_type->tp_richcompare()
, так и w->ob_type->tp_richcompare()
.
Для пользовательских классов tp_richcompare()
слот определяется как slot_tp_richcompare()
функция и эта функция снова выполняет __eq__
для обеих сторон, сначала self.__eq__(self, other)
, затем other.__eq__(other, self)
.
В конце это означает, что apple.__eq__(apple, orange)
и orange.__eq__(orange, apple)
вызывается для первой попытки в try_rich_compare()
, а затем вызывается обратное, что приводит к вызовам orange.__eq__(orange, apple)
и apple.__eq__(apple, orange)
как self
и other
меняются местами в slot_tp_richcompare()
.
Обратите внимание, что проблема ограничивается экземплярами разных пользовательских классов, где оба класса определяют метод __eq__
. Если обе стороны не имеют такого метода __eq__
выполняется только один раз:
>>> class Pear(object):
... def __init__(self, purpose):
... self.purpose = purpose
... def __repr__(self):
... return "<Pear purpose='{purpose}'>".format(purpose=self.purpose)
...
>>> pear = Pear("cooking")
>>> apple == pear
<Apple color='red'> == <Pear purpose='cooking'> -> NotImplemented
False
>>> pear == apple
<Apple color='red'> == <Pear purpose='cooking'> -> NotImplemented
False
Если у вас есть два экземпляра одного и того же типа, а __eq__
возвращает NotImplemented
, вы даже сравните шесть:
>>> class Kumquat(object):
... def __init__(self, variety):
... self.variety = variety
... def __repr__(self):
... return "<Kumquat variety=='{variety}'>".format(variety=self.variety)
... def __eq__(self, other):
... # Kumquats are a weird fruit, they don't want to be compared with anything
... print("{self} == {other} -> NotImplemented".format(self=self, other=other))
... return NotImplemented
...
>>> Kumquat('round') == Kumquat('oval')
<Kumquat variety=='round'> == <Kumquat variety=='oval'> -> NotImplemented
<Kumquat variety=='oval'> == <Kumquat variety=='round'> -> NotImplemented
<Kumquat variety=='round'> == <Kumquat variety=='oval'> -> NotImplemented
<Kumquat variety=='oval'> == <Kumquat variety=='round'> -> NotImplemented
<Kumquat variety=='oval'> == <Kumquat variety=='round'> -> NotImplemented
<Kumquat variety=='round'> == <Kumquat variety=='oval'> -> NotImplemented
False
Первый набор из двух сравнений был вызван из попытки оптимизации; когда два экземпляра имеют один и тот же тип, вам нужно только вызвать v->tp_richcompare(v, w)
, и принуждения (для чисел) могут быть пропущены в конце концов. Однако, когда это сравнение терпит неудачу (возвращается NotImplemented
), тогда также проверяется стандартный путь.
Сравнение сравнений в Python 2 довольно усложнилось, поскольку более старый __cmp__
метод сравнения с тремя способами все же должен был поддерживаться; в Python 3, с поддержкой __cmp__
отброшен, было легче исправить проблему. Таким образом, исправление никогда не поддерживалось до 2.7.