Post content
Нередко требуется удалять дубликаты инстансов класса. Для этого обычно используется либо циклы со сравнением некоторых атрибутов, либо тип данных set(). При добавлении элемента в set происходит сравнение этого объекта по хешу. Если хеш совпадает с хешем уже существующего объекта, то происходит сравнение объектов на равенство. Если объекты равны, то новый объект не добавляется. class A: def __init__(self, pk: int): self.pk = pk def __repr__(self): return f"{self.__class__.__name__}(pk={self.pk})" set([A(pk=1), A(pk=2), A(pk=2)]) >>> {A(pk=1), A(pk=2), A(pk=2)} Далее для краткости метод `__repr__()` я буду пропускать По умолчанию в расчёте хеша, помимо прочего, используется адрес в памяти, который можно получить с помощью функции id(), поэтому все объекты считаются разными. Чтобы изменить способ сравнения объектов нам требуется переопределить метод __eq__() class A: def __init__(self, pk: int): self.pk = pk def __eq__(self, other): return self.pk == other.pk set([A(pk=1), A(pk=2), A(pk=2)]) >>> TypeError: unhashable type: 'A' Теперь в дело вступает логика, описаная в документации. Если вы переопределили __eq__() то следует переопределить и __hash__(). class A: def __init__(self, pk: int): self.pk = pk def __eq__(self, other): return self.pk == other.pk def __hash__(self): return hash(self.pk) set([A(pk=1), A(pk=2), A(pk=2)]) >>> {A(pk=1), A(pk=2)} Отлично, теперь всё работает. Этот же принцип действует и при наследовании. Допустим, вы создали дочерний класс class B(A): pass set([B(pk=1), B(pk=2), B(pk=2)]) >>> {B(pk=1), B(pk=2)} Теперь следует учитывать вот такое поведение hash(A(1)) == hash(B(1)) >>> True set([A(1), B(1)]) >>> {A(pk=1)} Инстансы А и В могут считаться идентичными, если они имеют одинаковые значения атрибутов и хеш, что может привести к неожиданным результатам при использовании множеств. Нужно учесть это в методах: class A: ... def __eq__(self, other): return isinstance(other, self.__class__) and self.pk == other.pk def __hash__(self): return hash((self.pk, self.__class__)) ... Но если вдруг решите как-то изменить способ сравнения в классе В... class B(A): def __eq__(self, other): return abs(self.pk) == abs(other.pk) set([B(pk=1), B(pk=2), B(pk=2)]) >>> TypeError: unhashable type: 'B' Снова получите ошибку. Та же логика - при переопределении метода __eq__() в новом классе метод __hash__() автоматически становится None и его тоже требуется переопределить. #tricks