Рассмотрим эту простую проблему:
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
оператор, позволяющий отличать идентичность объекта от сравнения значений.