问题:比较对象实例的属性是否相等
我有一个类MyClass,其中包含两个成员变量foo和bar:
class MyClass:
    def __init__(self, foo, bar):
        self.foo = foo
        self.bar = bar
我有这样的类,其每个具有相同值的两个实例foo和bar:
x = MyClass('foo', 'bar')
y = MyClass('foo', 'bar')
但是,当我比较它们的相等性时,Python返回False:
>>> x == y
False
如何使python认为这两个对象相等?
回答 0
您应该实现以下方法__eq__:
class MyClass:
    def __init__(self, foo, bar):
        self.foo = foo
        self.bar = bar
    def __eq__(self, other): 
        if not isinstance(other, MyClass):
            # don't attempt to compare against unrelated types
            return NotImplemented
        return self.foo == other.foo and self.bar == other.bar现在它输出:
>>> x == y
True请注意,实现__eq__会自动使您的类的实例变得不可散列,这意味着它们无法存储在集合和字典中。如果您不对不可变的类型进行建模(即属性foo和属性bar可能在对象的生存期内更改),则建议仅使实例保持不可散列状态。
如果要对不可变类型进行建模,则还应该实现datamodel hook __hash__:
class MyClass:
    ...
    def __hash__(self):
        # necessary for instances to behave sanely in dicts and sets.
        return hash((self.foo, self.bar))__dict__不建议使用通用的解决方案,例如遍历和比较值的想法-它永远不可能真正通用,因为其中__dict__可能包含不可比较或不可哈希的类型。
注意:请注意,在Python 3之前,您可能需要使用__cmp__而不是__eq__。Python 2用户可能还想实现__ne__,因为不等式的明智的默认行为(即反转相等结果)不会在Python 2中自动创建。
回答 1
您将覆盖对象中的丰富比较运算符。
class MyClass:
 def __lt__(self, other):
      # return comparison
 def __le__(self, other):
      # return comparison
 def __eq__(self, other):
      # return comparison
 def __ne__(self, other):
      # return comparison
 def __gt__(self, other):
      # return comparison
 def __ge__(self, other):
      # return comparison像这样:
    def __eq__(self, other):
        return self._id == other._id回答 2
__eq__在您的类中实现该方法;像这样的东西:
def __eq__(self, other):
    return self.path == other.path and self.title == other.title编辑:如果您希望您的对象比较且仅当它们具有相等的实例字典时才比较:
def __eq__(self, other):
    return self.__dict__ == other.__dict__回答 3
总结一下:
- 建议您执行__eq__而不是__cmp__,除非您运行python <= 2.0(__eq__已在2.1中添加)
- 别忘了也要实现__ne__(应该是类似的东西return not self.__eq__(other)或return not self == other非常特殊的情况除外)
- 不要忘记,必须在要比较的每个自定义类中实现运算符(请参见下面的示例)。
- 如果要与可以为None的对象进行比较,则必须实现它。解释器无法猜测…(请参见下面的示例) - class B(object): def __init__(self): self.name = "toto" def __eq__(self, other): if other is None: return False return self.name == other.name class A(object): def __init__(self): self.toto = "titi" self.b_inst = B() def __eq__(self, other): if other is None: return False return (self.toto, self.b_inst) == (other.toto, other.b_inst)
回答 4
回答 5
使用Python 3.7(及更高版本)中的数据类,比较对象实例是否相等是一项内置功能。
数据类的反向移植适用于Python 3.6。
(Py37) nsc@nsc-vbox:~$ python
Python 3.7.5 (default, Nov  7 2019, 10:50:52) 
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from dataclasses import dataclass
>>> @dataclass
... class MyClass():
...     foo: str
...     bar: str
... 
>>> x = MyClass(foo="foo", bar="bar")
>>> y = MyClass(foo="foo", bar="bar")
>>> x == y
True回答 6
比较对象实例时,将__cmp__调用该函数。
如果默认情况下==运算符对您不起作用,则始终可以__cmp__为该对象重新定义函数。
编辑:
正如已经指出的那样,__cmp__自3.0起不推荐使用该功能。相反,您应该使用“丰富比较”方法。
回答 7
我编写了此代码并将其放在test/utils我项目的模块中。对于不是Class的情况,只需按计划进行,这将遍历两个对象并确保
- 每个属性都等于对应属性
- 不存在悬挂属性(仅在一个对象上存在的属性)
大…不性感 …但是噢,它起作用了!
def assertObjectsEqual(obj_a, obj_b):
    def _assert(a, b):
        if a == b:
            return
        raise AssertionError(f'{a} !== {b} inside assertObjectsEqual')
    def _check(a, b):
        if a is None or b is None:
            _assert(a, b)
        for k,v in a.items():
            if isinstance(v, dict):
                assertObjectsEqual(v, b[k])
            else:
                _assert(v, b[k])
    # Asserting both directions is more work
    # but it ensures no dangling values on
    # on either object
    _check(obj_a, obj_b)
    _check(obj_b, obj_a)您可以删除它,_assert并仅使用普通ol’ 对其进行清理,assert但失败时收到的消息将无济于事。
回答 8
您应该实现以下方法__eq__:
 class MyClass:
      def __init__(self, foo, bar, name):
           self.foo = foo
           self.bar = bar
           self.name = name
      def __eq__(self,other):
           if not isinstance(other,MyClass):
                return NotImplemented
           else:
                #string lists of all method names and properties of each of these objects
                prop_names1 = list(self.__dict__)
                prop_names2 = list(other.__dict__)
                n = len(prop_names1) #number of properties
                for i in range(n):
                     if getattr(self,prop_names1[i]) != getattr(other,prop_names2[i]):
                          return False
                return True回答 9
下面的工作(在我有限的测试中)是通过对两个对象层次结构进行深入比较而实现的。在处理各种情况下,包括对象本身或其属性为字典的情况。
def deep_comp(o1:Any, o2:Any)->bool:
    # NOTE: dict don't have __dict__
    o1d = getattr(o1, '__dict__', None)
    o2d = getattr(o2, '__dict__', None)
    # if both are objects
    if o1d is not None and o2d is not None:
        # we will compare their dictionaries
        o1, o2 = o1.__dict__, o2.__dict__
    if o1 is not None and o2 is not None:
        # if both are dictionaries, we will compare each key
        if isinstance(o1, dict) and isinstance(o2, dict):
            for k in set().union(o1.keys() ,o2.keys()):
                if k in o1 and k in o2:
                    if not deep_comp(o1[k], o2[k]):
                        return False
                else:
                    return False # some key missing
            return True
    # mismatched object types or both are scalers, or one or both None
    return o1 == o2这是一个非常棘手的代码,因此请在注释中添加任何可能不适合您的情况。
回答 10
class Node:
    def __init__(self, value):
        self.value = value
        self.next = None
    def __repr__(self):
        return str(self.value)
    def __eq__(self,other):
        return self.value == other.value
node1 = Node(1)
node2 = Node(1)
print(f'node1 id:{id(node1)}')
print(f'node2 id:{id(node2)}')
print(node1 == node2)>>> node1 id:4396696848
>>> node2 id:4396698000
>>> True回答 11
如果你正在处理一个或多个类,你不能从内部更改的,则有一些通用且简单的方法可以执行此操作,而这些方法也不依赖于diff特定的库:
最简单,非常不安全的复杂对象方法
pickle.dumps(a) == pickle.dumps(b)pickle是用于Python对象的非常常见的序列化库,因此实际上可以对几乎所有内容进行序列化。在上面的代码段中,我正在将strfrom序列化为afromb。与下一种方法不同,该方法还具有对自定义类进行类型检查的优点。
最大的麻烦:由于特定的排序和[de / en]编码方法,pickle对于相等的对象可能不会产生相同的结果,尤其是在处理更复杂的对象(例如,嵌套的自定义类实例的列表)时,您会经常发现在某些第三方库中。对于这些情况,我建议使用其他方法:
彻底安全的任何对象方法
您可以编写一个递归反射,该反射将为您提供可序列化的对象,然后比较结果
from collections.abc import Iterable
BASE_TYPES = [str, int, float, bool, type(None)]
def base_typed(obj):
    """Recursive reflection method to convert any object property into a comparable form.
    """
    T = type(obj)
    from_numpy = T.__module__ == 'numpy'
    if T in BASE_TYPES or callable(obj) or (from_numpy and not isinstance(T, Iterable)):
        return obj
    if isinstance(obj, Iterable):
        base_items = [base_typed(item) for item in obj]
        return base_items if from_numpy else T(base_items)
    d = obj if T is dict else obj.__dict__
    return {k: base_typed(v) for k, v in d.items()}
def deep_equals(*args):
    return all(base_typed(args[0]) == base_typed(other) for other in args[1:])现在不管您的对象是什么,都可以保证深度平等
>>> from sklearn.ensemble import RandomForestClassifier
>>>
>>> a = RandomForestClassifier(max_depth=2, random_state=42)
>>> b = RandomForestClassifier(max_depth=2, random_state=42)
>>> 
>>> deep_equals(a, b)
True可比对象的数量也无关紧要
>>> c = RandomForestClassifier(max_depth=2, random_state=1000)
>>> deep_equals(a, b, c)
False为此,我的用例是检查BDD测试中各种经过训练的机器学习模型之间的深层相等性。这些模型属于一组不同的第三方库。当然,__eq__像其他答案一样在这里实施对我来说不是一个选择。
覆盖所有基地
您可能处于一个或多个被比较的自定义类没有__dict__实现的情况下。无论如何,这并不普遍,但是sklearn的Random Forest分类器中的一个子类型就是这种情况<type 'sklearn.tree._tree.Tree'>。在逐个案例的情况下处理这些情况-例如,特别是,我决定用为我提供有关实例的代表性信息的__getstate__方法(在这种情况下,该方法)的内容替换患病类型的内容。为此,倒数第二行base_typed成为
d = obj if T is dict else obj.__dict__ if '__dict__' in dir(obj) else obj.__getstate__()编辑:为组织着想,我取代的最后两行base_typed用return dict_from(obj),并实现了真正通用的反映它容纳更多的晦涩库(我看着你,Doc2Vec)
def isproperty(prop, obj):
    return not callable(getattr(obj, prop)) and not prop.startswith('_')
def dict_from(obj):
    """Converts dict-like objects into dicts
    """
    if isinstance(obj, dict):
        # Dict and subtypes are directly converted
        d = dict(obj)
    elif '__dict__' in dir(obj):
        d = obj.__dict__
    elif str(type(obj)) == 'sklearn.tree._tree.Tree':
        # Replaces sklearn trees with their state metadata
        d = obj.__getstate__()
    else:
        # Extract non-callable, non-private attributes with reflection
        kv = [(p, getattr(obj, p)) for p in dir(obj) if isproperty(p, obj)]
        d = {k: v for k, v in kv}
    return {k: base_typed(v) for k, v in d.items()}请注意,上述方法都不True会对具有相同键值对但键/值顺序不同的不同对象产生,如
>>> a = {'foo':[], 'bar':{}}
>>> b = {'bar':{}, 'foo':[]}
>>> pickle.dumps(a) == pickle.dumps(b)
False但是,如果您愿意,则可以sorted预先使用Python的内置方法。
回答 12
如果要获得逐个属性的比较,并查看它是否失败以及在何处失败,则可以使用以下列表理解:
[i for i,j in 
 zip([getattr(obj_1, attr) for attr in dir(obj_1)],
     [getattr(obj_2, attr) for attr in dir(obj_2)]) 
 if not i==j]在此的额外好处是,您可以将其压缩一行,然后在PyCharm中进行调试时输入“评估表达式”窗口。
回答 13
我尝试了初始示例(请参见上面的7),但在ipython中不起作用。请注意,使用两个相同的对象实例实现时,cmp(obj1,obj2)返回“ 1”。奇怪的是,当我修改一个属性值并重新比较时,使用cmp(obj1,obj2),该对象将继续返回“ 1”。(叹…)
好的,所以您需要做的是迭代两个对象,并使用==符号比较每个属性。
回答 14
与==比较时,类的实例不相等。最好的方法是屁股CMP函数添加到您的类中,这将完成任务。
如果要按内容进行比较,可以简单地使用cmp(obj1,obj2)
在您的情况下,如果cmp(doc1,doc2)的内容相同,则它将返回-1。

