How do I check for equality using super-class-level comparison?
from dataclasses import dataclass
@dataclass(order=True)
class Base:
foo: int
@dataclass(order=True)
class X(Base):
x: str
@dataclass(order=True)
class Y(Base):
y: float
x = X(0, "x")
y = Y(1, 1.0)
How do I compare x
and y
on their Base
attributes only?
Base.__eq__(x,y)
seems to do the job, is this the "pythonic" way?
How do I check for equality using super-class-level comparison?
from dataclasses import dataclass
@dataclass(order=True)
class Base:
foo: int
@dataclass(order=True)
class X(Base):
x: str
@dataclass(order=True)
class Y(Base):
y: float
x = X(0, "x")
y = Y(1, 1.0)
How do I compare x
and y
on their Base
attributes only?
Base.__eq__(x,y)
seems to do the job, is this the "pythonic" way?
3 Answers
Reset to default 0
Base.__eq__(x, y)
seems to do the job
On interpreter 3.12 it reports:
>>> Base.__eq__(x, y)
NotImplemented
You can use .mro()
to identify commonality between x
and y
:
>>> type(x).mro()
[<class '__main__.X'>, <class '__main__.Base'>, <class 'object'>]
Having established that Base
is common, we could build a new
pair of Base
objects, which are readily compared:
>>> list(x.__dataclass_fields__.keys())
['foo', 'x']
>>> list(Base.__dataclass_fields__.keys())
['foo']
>>>
>>> getattr(x, 'foo')
0
>>> x_b = Base(foo=getattr(x, 'foo'))
>>> y_b = Base(foo=getattr(y, 'foo'))
>>> y_b
Base(foo=1)
>>> x_b == y_b
False
>>>
>>> y_b = Base(foo=0)
>>> x_b == y_b
True
Implement __eq__
in Base
and make the children use it.
from dataclasses import dataclass
@dataclass(order=True)
class Base:
foo: int
def __eq__(self, other):
return isinstance(other, Base) and other.foo == self.foo
@dataclass(order=True)
class X(Base):
x: str
def __eq__(self, other):
return super().__eq__(other)
@dataclass(order=True)
class Y(Base):
y: float
def __eq__(self, other):
return super().__eq__(other)
x = X(0, "x")
y = Y(1, 1.0)
z = X(0, 1.0)
print(x == y)
print(x == z)
Output:
False
True
We have to implement a mechanism that allows us to perform the comparison.
Calling super from eq is not a good idea, because although it allows us to make the comparison son to parent or sibling to sibling, it does not eliminate the possibility of comparing objects of the same class, that is: xA == xB will give an uncertain result.
One option is to make the implementation of eq consider this situation, the other is to create auxiliary parent objects and compare them (I don't like it).
from dataclasses import dataclass
@dataclass(order=True)
class Base:
foo: int
def __eq__( self, other ):
if isinstance( other, Base ) and other.foo == self.foo:
return True
return False
@dataclass(order=True)
class X( Base ):
x: str
def __eq__( self, other ):
if isinstance( other, X ):
return other.x == self.x and other.foo == self.foo
if isinstance( other, Y ) or isinstance( other, Base ):
return other.foo == self.foo
return False
@dataclass(order=True)
class Y( Base ):
y: float
def __eq__(self, other):
if isinstance( other, Y ):
return other.y == self.y and other.foo == self.foo
if isinstance( other, X ) or isinstance( other, Base ):
return other.foo == self.foo
return False
xA = X( 1, "x" )
xB = X( 1, "m" )
y = Y( 1, 1.0 )
z = X( 0, 1.0 )
print( xA == y )
print( xA == z )
print( xA == xB )
Edit: Correction to eq of the Base class suggested by @ juanpa.arrivillaga
Edit: Correction:
The code proposed in the previous lines has the same defect it claims to solve: it does not allow a reliable comparison of two objects of the same class. Therefore, we can state that:
The eq method should only return "True" if the object received as a parameter is of the same class and its attributes have the same values, so in this case, it should be implemented like this:
In Base
def __eq__( self, other ):
return isinstance( other, Base ) and other.foo == self.foo
In Y
def __eq__( self, other ):
return isinstance( other, Y ) and other.foo == self.foo and other.y == self.y
In X
def __eq__( self, other ):
return isinstance( other, X ) and other.foo == self.foo and other.x == self.x
To perform the desired comparison, we can implement the following method in Base:
def compareWithFamilyMember( self, other ):
return isinstance( other, Base ) and other.foo == self.foo
base1 = Base( 1 )
y = Y( 1, 1.0 )
print( base1 == y ) ### return False
print( base1pareWithFamilyMember( y ) ) ### return True
==
or you just want to do this as an alternative – juanpa.arrivillaga Commented Mar 17 at 18:06Base.__eq__(x,y)
returnsNotImplemented
, btw... – juanpa.arrivillaga Commented Mar 17 at 18:09dataclasses
you could just define__eq__
forBase
only and omit for the inherited classes to get the superclass__eq__
to be called. Not sure how to tweak it fordataclasses
but maybe the documentation has a blurb about this – wLui155 Commented Mar 17 at 18:11__eq__
only compares if the two objects have exactly the same type, explicitly checking:if other.__class__ is self.__class__
see here. You could, of course, write your ownBase.__eq__
that doesn't do this, then useBase.__eq__
in the other classes like so__eq__ = Base.__eq__
– juanpa.arrivillaga Commented Mar 17 at 18:12(order=True)
creates__eq__
method – sds Commented Mar 17 at 18:25