-
-
Notifications
You must be signed in to change notification settings - Fork 411
Description
Motivation and Problem
In my code, it's a common idiom to have a two classes called, e.g., SomethingMutable and SomethingFrozen; these are both attrs classes decorated with @attrs.define and @attrs.frozen, respectively. Their field definitions are "the same" with minor type differences to account for mutability (list vs. tuple, set vs. frozenset, etc.)
I'd like instances of SomethingMutable and SomethingFrozen to compare equal IFF every the values for the defined fields are all equal.
What doesn't work
The attrs-generated __eq__() always checks instance types are the same before checking that the field values match, meaning that instances of different attrs classes can never compare equal. Consequently, overriding __eq__() and calling super() isn't helpful.
There's no obvious way to leverage the attrs-generated, field-by-field comparisons currently in __eq__() unless both instances are exactly the same type.
Possible approaches
It would be very helpful if there were a way to tell attrs to ignore the classes when comparing, and consider only those fields defined by the left instance's class.
This could take a number of forms, such as:
__eq__()could take an optional keyword argumentignore_type: bool, intended for use viasuper()attrscould break__eq__()into two steps: one to check instance types, and one to check field valuesclassmethod[__attrs_types_equal__(cls, other: type) -> bool]
would replace today's__eq__()'s first step, i.e., roughly:if type(other) is not type(self): return False__attrs_fields_equal__(self, other: Any) -> bool
would check that every field inselfis matched by an attribute ofotherwhich has an equal value (or whateverattrs.field(eq=...)says to do)- for example
- if
class Child(Parent): ...adds at least one field not found inParent - and given:
c: Child; p: Parent - then it would typically be true that
p.__attrs_fields_equal__(c),- since
p._afe_(c)considers only fields inp, all of which are also present inc(per the LSP/Liskov Substitution Principle),
- since
- ... but typically false that
c.__attrs_fields_equal__(p)- since
c._afe_(p)considers only fields inc, some of which are absent fromp(by assumption)
- since
- if
- for example
__eq__()could be replaced by just a few, static lines:return ( self.__attrs_types_equal__(type(other)) and self.__attrs_fields_equal__(other) )
- breaking up
__eq__()might carry a performance penalty- but I think it should usually be possible to detect cases where a bifurcated implementation is called for, add an override flag to
attrs.define()(etc.) for edge cases, and use a unified implementation (as we do today) where we can __eq__()could also check whethertype(self).__attrs_types_equal__is the unmodifiedattrs-provided version (identity comparison); and, if so, use an inlined copy of the same generated code to save the overhead of the method call- (...and similarly for
__attrs_fields_equal__()) - this should be safe even even where there's monkeypatching
- (...and similarly for
- but I think it should usually be possible to detect cases where a bifurcated implementation is called for, add an override flag to
- breaking up