通过Python中的属性比较对象实例是否相等

时间:2009-08-04 12:09:39

标签: python equality

我有一个班级MyClass,其中包含两个成员变量foobar

class MyClass:
    def __init__(self, foo, bar):
        self.foo = foo
        self.bar = bar

我有两个这个类的实例,每个实例都有foobar的相同值:

x = MyClass('foo', 'bar')
y = MyClass('foo', 'bar')

但是,当我比较它们是否相等时,Python会返回False

>>> x == y
False

如何让python认为这两个对象相等?

16 个答案:

答案 0 :(得分:287)

您应该实施方法__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__会自动使您的类的实例不可用,这意味着它们不能存储在集合和dicts中。如果您没有为不可变类型建模(即,如果属性foobar可能会在对象的生命周期内更改值),那么建议您将实例保持为不可用。

如果要为不可变类型建模,还应该实现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__可能包含无法比较或不可用的类型。

N.B。:请注意,在Python 3之前,您可能需要使用__cmp__而不是__eq__。 Python 2用户可能还想实现__ne__,因为不等式的明确默认行为(即反转相等结果)将不会在Python 2中自动创建。

答案 1 :(得分:44)

您覆盖对象中的rich comparison operators

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 :(得分:5)

在班级中实施__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 :(得分:4)

总结:

  1. 建议实施__eq__而不是__cmp__,除非您运行python< = 2.0(已在2.1中添加__eq__
  2. 不要忘记同时实施__ne__(应该是return not self.__eq__(other)return not self == other,除非是特殊情况)
  3. 不要忘记必须在要比较的每个自定义类中实现运算符(参见下面的示例)。
  4. 如果要与可以为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 :(得分:4)

如果您要处理的是内部无法更改的一个或多个类,则有一些通用且简单的方法可以完成此操作,这些方法也不依赖于特定于diff的库:

最简单,不安全的非常复杂的对象方法

pickle.dumps(a) == pickle.dumps(b)

pickle是一个非常常见的Python对象序列化库,因此实际上可以序列化几乎所有东西。在上面的代码段中,我将序列化str的{​​{1}}与a的序列进行比较。与下一种方法不同,该方法还具有对自定义类进行类型检查的优点。

最大的麻烦:由于特定的排序和[de / en]编码方法pickle may not yield the same result for equal objects,尤其是在处理更复杂的方法(例如嵌套的自定义类实例的列表)时,就像您经常在一些第三方库。对于这些情况,我建议使用其他方法:

彻底的任何对象安全方法

您可以编写一个递归的反射,该反射将为您提供可序列化的对象,然后比较结果

b

现在不管您的对象是什么,都可以保证深度平等工作

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

为此,我的用例是检查BDD测试中各种经过训练有素的机器学习模型之间的深层相等性。这些模型属于一组不同的第三方库。当然,像这里的其他答案一样实现>>> c = RandomForestClassifier(max_depth=2, random_state=1000) >>> deep_equals(a, b, c) False 并不是我的选择。

涵盖所有基地

您可能处在一个或多个要比较的自定义类没有__eq__实现的情况下。无论如何,这并不普遍,但是sklearn的“随机森林”分类器中的一个子类型就是这种情况:__dict__。根据具体情况处理这些情况-例如特别是,我决定用一种为我提供有关实例的代表性信息的方法(在这种情况下为<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__() 替换了上面丑陋的oneliner。在这里,return dict_from(obj)是一个真正通用的反映,用于容纳更多晦涩的lib(我正在看着您,Doc2Vec)

dict_from

请注意,上述方法中的都不会对具有相同键值对的对象产生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): # Use standard dict representation when available 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

但是,如果您希望可以始终使用Python内置的>>> a = {'foo':[], 'bar':{}} >>> b = {'bar':{}, 'foo':[]} >>> pickle.dumps(a) == pickle.dumps(b) False 方法。

答案 5 :(得分:3)

使用Dataclasses in Python 3.7(及更高版本),比较对象实例是否相等是一项内置功能。<​​/ p>

backport for Dataclasses适用于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 :(得分:2)

比较对象的实例时,会调用__cmp__函数。

如果默认情况下==运算符不适合您,您可以随时重新定义对象的__cmp__函数。

编辑:

正如已经指出的那样,__cmp__函数自3.0起就被弃用了。 相反,您应该使用“rich comparison”方法。

答案 7 :(得分:1)

根据您的具体情况,您可以执行以下操作:

/* Everything in the window adopts the style, 
 including the root view controller and all presentation controllers that 
 display content in that window.*/
window.overrideUserInterfaceStyle = .light

请参见Python dictionary from an object's fields

答案 8 :(得分:0)

我编写了此文件,并将其放在项目的test/utils模块中。对于不是班级的情况,只需按计划进行,这将遍历两个对象并确保

  1. 每个属性都等于它的对应物
  2. 不存在悬空属性(仅在一个对象上存在的物)

它很大...不是性感 ...但是哦,它能起作用!

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来对其进行清理,但是失败时收到的消息将非常无助。

答案 9 :(得分:0)

您应该实现方法__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

答案 10 :(得分:0)

下面的工作(在我的有限测试中)是通过在两个对象层次结构之间进行深层比较来完成的。 In处理各种情况,包括对象本身或其属性为字典的情况。

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

这是一个非常棘手的代码,因此请在注释中添加任何可能不适合您的情况。

答案 11 :(得分:0)

使用 setattr 函数。当您无法在类本身中添加某些内容时,例如在导入类时,您可能想要使用它。

setattr(MyClass, "__eq__", lambda x, y: x.foo == y.foo and x.bar == y.bar)

答案 12 :(得分:-1)

如果您想获得逐个属性的比较,并查看它是否失败,您可以使用以下列表理解:

[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中调试时进入“Evaluate Expression”窗口。

答案 13 :(得分:-1)

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

答案 14 :(得分:-2)

我尝试了最初的例子(见上面的7)并且它在ipython中不起作用。请注意,当使用两个相同的对象实例实现时,cmp(obj1,obj2)返回“1”。奇怪的是,当我修改其中一个属性值并重新比较时,使用cmp(obj1,obj2),对象继续返回“1”。 (叹气...)

好的,所以你需要做的是迭代两个对象并使用==符号比较每个属性。

答案 15 :(得分:-5)

与==相比,类的实例变得不相等。最好的方法是将 cmp 函数添加到您的类中,这将完成这些操作。

如果你想通过内容进行比较,你可以简单地使用cmp(obj1,obj2)

在你的情况下cmp(doc1,doc2)如果内容明智,它将返回-1。