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