Рассмотрим эту простую проблему:
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__()чтобы операторы вели себя так, как ожидалось.
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значение как:
Числовые методы и методы расширенного сравнения могут возвращать это значение, если они не реализуют операцию для предоставленных операндов. (Затем интерпретатор попытается выполнить отраженную операцию или какой-либо другой запасной вариант, в зависимости от оператора.) Его истинное значение равно true.
В этом случае делегаты оператора операция сравнения на отражение метод от другого операнда. В документации определяет отражение методы , как:
Не существует версий этих методов со свопированными аргументами (которые будут использоваться, когда левый аргумент не поддерживает операцию, а правый аргумент поддерживает); а, __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 NotImplemented:
return NotImplemented
return not x
Возвращение 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
isоператор, позволяющий отличать идентичность объекта от сравнения значений.