最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

python - How to compare objects based on their superclass - Stack Overflow

programmeradmin4浏览0评论

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?

Share Improve this question edited Mar 17 at 18:22 sds asked Mar 17 at 17:30 sdssds 60.2k31 gold badges175 silver badges298 bronze badges 8
  • it isn't clear if you want this to be how the objects are actually compared with == or you just want to do this as an alternative – juanpa.arrivillaga Commented Mar 17 at 18:06
  • Base.__eq__(x,y) returns NotImplemented, btw... – juanpa.arrivillaga Commented Mar 17 at 18:09
  • If you weren't beholden to dataclasses you could just define __eq__ for Base only and omit for the inherited classes to get the superclass __eq__ to be called. Not sure how to tweak it for dataclasses but maybe the documentation has a blurb about this – wLui155 Commented Mar 17 at 18:11
  • But yeah, basically, you are working against the dataclasses machinery. Basically the generated __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 own Base.__eq__ that doesn't do this, then use Base.__eq__ in the other classes like so __eq__ = Base.__eq__ – juanpa.arrivillaga Commented Mar 17 at 18:12
  • @juanpa.arrivillaga: (order=True) creates __eq__ method – sds Commented Mar 17 at 18:25
 |  Show 3 more comments

3 Answers 3

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

发布评论

评论列表(0)

  1. 暂无评论