Ответ 1
Рассмотрим эту простую задачу:
class Number:
def __init__(self, number):
self.number = number
n1 = Number(1)
n2 = Number(1)
n1 == n2 # False -- oops
Таким образом, Python по умолчанию использует идентификаторы объектов для операций сравнения:
id(n1) # 140400634555856
id(n2) # 140400634555920
Переопределение функции __eq__
похоже, решает проблему:
def __eq__(self, other):
"""Overrides the default implementation"""
if isinstance(other, Number):
return self.number == other.number
return False
n1 == n2 # True
n1 != n2 # True in Python 2 -- oops, False in Python 3
В Python 2 всегда помните о переопределении функции __ne__
, так как в документации указано:
Между операторами сравнения нет подразумеваемых отношений. Истина
x==y
не означает, чтоx!=y
ложно. Соответственно, при определении__eq__()
следует также определить__ne__()
чтобы операторы__ne__()
образом.
def __ne__(self, other):
"""Overrides the default implementation (unnecessary in Python 3)"""
return not self.__eq__(other)
n1 == n2 # True
n1 != n2 # False
В Python 3 это больше не требуется, поскольку в документации указано:
По умолчанию
__ne__()
делегирует__eq__()
и инвертирует результат, если не являетсяNotImplemented
. Других подразумеваемых отношений между операторами сравнения нет, например, истина(x<y or x==y)
не означаетx<=y
.
Но это не решает всех наших проблем. Давайте добавим подкласс:
class SubNumber(Number):
pass
n3 = SubNumber(1)
n1 == n3 # False for classic-style classes -- oops, True for new-style classes
n3 == n1 # True
n1 != n3 # True for classic-style classes -- oops, False for new-style classes
n3 != n1 # False
Примечание. Python 2 имеет два типа классов:
-
классические (или старые) классы, которые не наследуются от
object
и объявляются какclass A:
class A():
илиclass A(B):
гдеB
- класс классического стиля; -
классы нового стиля, которые наследуют от
object
и объявляются какclass A(object)
илиclass A(B):
гдеB
- класс нового стиля. Python 3 имеет только классы нового стиля, объявленные какclass A:
class A(object):
илиclass A(B):
Для классов классического класса операция сравнения всегда вызывает метод первого операнда, тогда как для классов нового стиля он всегда вызывает метод операнда подкласса, независимо от порядка операндов.
Итак, если Number
является классом классического стиля:
-
n1 == n3
вызываетn1.__eq__
; -
n3 == n1
вызываетn3.__eq__
; -
n1 != n3
вызываетn1.__ne__
; -
n3 != n1
вызываетn3.__ne__
.
И если Number
является классом нового стиля:
- оба
n1 == n3
иn3 == n1
вызываютn3.__eq__
; - оба
n1 != n3
иn3 != n1
вызываютn3.__ne__
.
Чтобы исправить проблему несовместимости операторов ==
и !=
Для классов классического стиля Python 2, методы __eq__
и __ne__
должны возвращать значение NotImplemented
если тип операнда не поддерживается. Документация определяет значение NotImplemented
как:
Числовые методы и богатые методы сравнения могут вернуть это значение, если они не реализуют операцию для предоставленных операндов. (Интерпретатор затем попробует отраженную операцию или какой-либо другой резерв, в зависимости от оператора.) Его истинное значение истинно.
В этом случае оператор делегирует операцию сравнения методу отражения другого операнда. Документация определяет отраженные методы как:
Вариантов этих методов нет (для использования, когда левый аргумент не поддерживает операцию, но правильный аргумент); скорее,
__lt__()
и__gt__()
являются отражением друг друга,__le__()
и__ge__()
являются отражением друг друга, а__eq__()
и__ne__()
являются их собственным отражением.
Результат выглядит следующим образом:
def __eq__(self, other):
"""Overrides the default implementation"""
if isinstance(other, Number):
return self.number == other.number
return NotImplemented
def __ne__(self, other):
"""Overrides the default implementation (unnecessary in Python 3)"""
x = self.__eq__(other)
if x is not NotImplemented:
return not x
return NotImplemented
Возвращение значения NotImplemented
вместо False
- это правильная вещь, которую нужно делать даже для классов нового стиля, если требуется, чтобы операнды операторов ==
и !=
Были необходимы, когда операнды имеют несвязанные типы (без наследования).
Мы уже на месте? Не совсем. Сколько у нас уникальных номеров?
len(set([n1, n2, n3])) # 3 -- oops
Наборы используют хеши объектов, и по умолчанию Python возвращает хэш идентификатора объекта. Давайте попробуем переопределить его:
def __hash__(self):
"""Overrides the default implementation"""
return hash(tuple(sorted(self.__dict__.items())))
len(set([n1, n2, n3])) # 1
Конечный результат выглядит следующим образом (я добавил некоторые утверждения в конце для проверки):
class Number:
def __init__(self, number):
self.number = number
def __eq__(self, other):
"""Overrides the default implementation"""
if isinstance(other, Number):
return self.number == other.number
return NotImplemented
def __ne__(self, other):
"""Overrides the default implementation (unnecessary in Python 3)"""
x = self.__eq__(other)
if x is not NotImplemented:
return not x
return NotImplemented
def __hash__(self):
"""Overrides the default implementation"""
return hash(tuple(sorted(self.__dict__.items())))
class SubNumber(Number):
pass
n1 = Number(1)
n2 = Number(1)
n3 = SubNumber(1)
n4 = SubNumber(4)
assert n1 == n2
assert n2 == n1
assert not n1 != n2
assert not n2 != n1
assert n1 == n3
assert n3 == n1
assert not n1 != n3
assert not n3 != n1
assert not n1 == n4
assert not n4 == n1
assert n1 != n4
assert n4 != n1
assert len(set([n1, n2, n3, ])) == 1
assert len(set([n1, n2, n3, n4])) == 2