Как оптимизировать метод __eq__ без использования if и getattr
Что проверяют
Знаете ли вы протокол сравнения в Python: NotImplemented, отражённый вызов, связь __eq__ с __hash__. Запрет на if и getattr проверяет, владеете ли вы идиомами вместо цепочки проверок по каждому полю. Следом обычно спрашивают, что станет с хэшем и можно ли такой объект положить в set.
Как отвечать
Покажите несколько приёмов и честно назовите их цену:
- кортеж атрибутов:
(self.a, self.b) == (other.a, other.b). Сравнение идёт поэлементно и останавливается на первом несовпадении, всё в одной строке; operator.attrgetter("a", "b"): геттер, созданный один раз на уровне класса, возвращает тот же кортеж, а список полей живёт в одном месте;NotImplementedбезif: обернуть сравнение вtryи вexcept AttributeErrorвернутьNotImplemented. Минус: равным признается любой объект с полямиaиb. Если нужна строгая проверка класса, лучше прямо сказать, что условие здесь оправдано;@dataclass: генерирует__eq__сам, сравнивает поля в порядке объявления и для объекта другого класса возвращаетNotImplemented. Лишнее поле исключают черезfield(compare=False);__slots__: ускоряет доступ к атрибутам и экономит память, когда объектов миллионы.
Обязательно скажите про хэш: класс, который определил __eq__ без __hash__, получает __hash__ = None, и объект больше не кладётся в set и ключи dict. Для неизменяемых объектов берут @dataclass(frozen=True) или пишут hash((self.a, self.b)) по тем же полям. И про «оптимизацию» честно: выигрыш здесь в наносекундах, главное это корректность и один список полей, а мерить стоит через timeit.
Где спотыкаются
Возвращают False вместо NotImplemented для чужого типа. Python тогда не пробует отражённое other.__eq__(self), и сравнение перестаёт быть симметричным у наследников и прокси.
Сравнивают через frozenset атрибутов. Объекты с a=1, b=2 и a=2, b=1 становятся равными, одинаковые значения схлопываются, а поле со списком роняет сравнение с TypeError.
Пишут self.__dict__ == other.__dict__. Туда попадают кэши и служебные атрибуты, а у классов со __slots__ словаря нет вовсе.