Python中的通用__eq__()方法

8
我想为以下类创建一个通用的__eq__()方法。基本上,我想添加另一个属性(nick),而不必更改__eq__()。
我想通过迭代dir()来实现这一点,但我想知道是否有一种方法可以创建一个推导式,只提供属性。
 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')

将数据存储在字典中。然后可以检查两个字典的等价性。与其使用属性,请考虑重写__getattr__(https://dev59.com/0GQo5IYBdhLWcg3wMtC2)。 - MEE
3
你有没有注意到你定义了两次 first.setter - user2357112
谢谢。这就是我复制粘贴的结果。 - Ray Salemi
3个回答

8
你可以使用__dict__来检查是否一致,适用于所有属性:
如果对象不是相同的类型,我会返回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

这可能是一种方法,但它无法处理派生属性。例如,我可能想要说`return self.full == other.full`并使用一个派生属性列表。 - Ray Salemi
1
@RaySalemi 怎么会呢?按照定义,派生属性不是应该相等吗,如果所有其他属性(它们可能派生自)都相等的话? - tobias_k
@RaySalemi,除非您在生成派生属性时有一些随机性,否则这仍然有效。 - user3483203
是的。但你看到了比较实际属性的更广泛的观点。 - Ray Salemi
@RaySalemi 你也可以尝试 return dir(self) == dir(other) && self.__dict__ == other.__dict__,虽然看起来有些冗余。 - user3483203

2
您可以使用以下结构获取Class的所有属性:
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)}
__eq__ 将变成这样:
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

使用reversed(cls.mro())的原因是可以让类似下面这样的代码正常运行:

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

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

print(p4 == p3)
True

太棒了!谢谢! - Ray Salemi
2
这完全是杀鸡焉用牛刀。相较于 vars(self) == vars(other) 这个易读的方法,这个难以理解的代码有什么优势呢? - Aran-Fey
vars(self) == vars(others) 如何处理使用某些算法创建值的属性? - Ray Salemi
@RaySalemi 它会忽略它们。但这并不重要,因为属性(通常)根据其他实例属性计算其结果,只要所有实例属性相等,属性值也将相等。当然,如果您有一个返回 random() 或类似内容的奇怪属性,那可能会有问题。 - Aran-Fey
1
@Aran-Fey 那个可怕的东西是我为了能够使用继承属性而想出来的。如果你认为你的解决方案更好,那么请随意将其发布为答案。 - Maarten Fabré

-1

你可以尝试这样做,如果你想在字典和集合中使用eq,它也会起作用。

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)))))

1
永远不要根据对象的哈希值进行比较。 - Aran-Fey
另外,你为什么要过滤和排序那个“__make_hash”列表?在我看来完全没有必要。 - Aran-Fey
我使用 slots = [] 并且只是复制粘贴代码的一部分。 - kojibhy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接