8

I'd like to create a generalized __eq__() method for the following Class. Basically I'd like to be able to add another property (nick) without having to change __eq__()

I imagine I can do this somehow by iterating over dir() but I wonder if there is a way to create a comprehension that just delivers the properties.

 class Person:
     def __init__(self, first, last):
         self.first=first
         self.last=last

     @property
     def first(self):
         assert(self._first != None)
         return self._first
     @first.setter
     def first(self,fn):
         assert(isinstance(fn,str))
         self._first=fn

     @property
     def last(self):
         assert(self._last != None)
         return self._last
     @last.setter
     def last(self,ln):
         assert(isinstance(ln,str))
         self._last=ln
     @property
     def full(self):
         return f'{self.first} {self.last}'

     def __eq__(self, other):
         return self.first==other.first and self.last==other.last

 p = Person('Raymond', 'Salemi')
 p2= Person('Ray', 'Salemi')
Ray Salemi
  • 5,247
  • 4
  • 30
  • 63
  • Store the data in a dictionary. Then you can check for equivalence of the two dictionaries. Instead of using properties consider overwriting `__getattr__` (https://stackoverflow.com/questions/16237659/python-how-to-implement-getattr) – MEE Mar 09 '18 at 15:54
  • 3
    Did you notice you defined `first.setter` twice? – user2357112 Mar 09 '18 at 15:58
  • Thanks. That's what I get for cut-and-paste. – Ray Salemi Mar 09 '18 at 16:02

3 Answers3

8

You could use __dict__ to check if everything is the same, which scales for all attributes:

If the objects are not matching types, I simply return False.

class Person:
    def __init__(self, first, last, nick):
        self.first = first
        self.last = last
        self.nick = nick

    def __eq__(self, other):
        return self.__dict__ == other.__dict__  if type(self) == type(other) else False

>>> p = Person('Ray', 'Salemi', 'Ray')
>>> p2= Person('Ray', 'Salemi', 'Ray')
>>> p3 = Person('Jared', 'Salemi', 'Jarbear')

>>> p == p2
True
>>> p3 == p2
False
>>> p == 1
False
user3483203
  • 50,081
  • 9
  • 65
  • 94
  • This might be a way to go, but it won't handle derived properties. For example, I might want to say `return self.full == other.full` And use a list of derived properties. – Ray Salemi Mar 09 '18 at 16:01
  • 1
    @RaySalemi How so? Shouldn't derived properties, pretty much by definition, be equal if all the other properties (that they could possibly be derived from) are equal? – tobias_k Mar 09 '18 at 16:03
  • @RaySalemi Unless you have some randomness with how you generate derived properties this should still work. – user3483203 Mar 09 '18 at 16:03
  • Yes. But you see the broader point about comparing actual properties. – Ray Salemi Mar 09 '18 at 16:08
  • @RaySalemi you could also try `return dir(self) == dir(other) && self.__dict__ == other.__dict__`, although it seems redundant. – user3483203 Mar 09 '18 at 16:21
2

You can get all the properties of a Class with a construct like this:

from itertools import chain
@classmethod
def _properties(cls):
    type_dict = dict(chain.from_iterable(typ.__dict__.items() for typ in reversed(cls.mro())))
    return {k for k, v in type_dict.items() if 'property' in str(v)}

The __eq__ would become something like this:

def __eq__(self, other):
    properties = self._properties() & other._properties()
    if other._properties() > properties and self._properties() > properties:
        # types are not comparable
        return False
    try:
        return all(getattr(self, prop) == getattr(other, prop) for prop in properties)
    except AttributeError:
        return False

The reason to work with the reversed(cls.mro()) is so something like this also works:

class Worker(Person):
    @property
    def wage(self):
        return 0

p4 = Worker('Raymond', 'Salemi')

print(p4 == p3)
True
Maarten Fabré
  • 6,938
  • 1
  • 17
  • 36
  • That nails it! Thank you! – Ray Salemi Mar 09 '18 at 17:10
  • 2
    This is complete overkill. What advantages does this unreadable monstrosity have over `vars(self) == vars(other)`? – Aran-Fey Mar 09 '18 at 21:48
  • How does vars(self) == vars(others) handle properties that create a value using some algorithm? – Ray Salemi Mar 10 '18 at 20:34
  • @RaySalemi It ignores them. But that doesn't matter, because properties (usually) calculate their results based on other instance attributes, so as long as all instance attributes are equal, the property values will also be equal. Of course, if you have a weird property that returns `random()` or something similar, then that could be a problem. – Aran-Fey Mar 11 '18 at 13:22
  • 1
    @Aran-Fey that monstrosity is what I came up with to also work with inherited properties. If you think your solution is better, then feel free to post it as an answer – Maarten Fabré Mar 11 '18 at 14:12
-1

you can try to do this, it will also work if you want eq inside dict and set

def __eq__(self, other):
    """Overrides the default implementation"""
    if isinstance(self, other.__class__):
        return self.__hash__() == other.__hash__()

    return NotImplemented

def __hash__(self):
    """Overrides the default implementation,
       and set which fieds to use for hash generation
    """
    __make_hash = [
         self.first
    ]
    return hash(tuple(sorted(list(filter(None, __make_hash)))))
kojibhy
  • 51
  • 1
  • 5