按对象实例的属性比较对象实例的相等性

Compare object instances for equality by their attributes

提问人:Željko Živković 提问时间:8/4/2009 最后编辑:Kyle F. HartzenbergŽeljko Živković 更新时间:9/14/2023 访问量:351339

问:

我有一个类,它包含两个成员变量和:MyClassfoobar

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 认为这两个对象相等?

Python 平等

评论


答:

1赞 Silfverstrom 8/4/2009 #1

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

如果 == 运算符在默认情况下不起作用,则始终可以重新定义对象的函数。__cmp__

编辑:

如前所述,该函数自 3.0 起已弃用。 相反,您应该使用“丰富比较”方法。__cmp__

评论

3赞 Christopher 8/4/2009
cmp 函数在 3.0+ 中已弃用
9赞 Kiv 8/4/2009 #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__

评论

0赞 S.Lott 8/4/2009
也许你的意思是看看它们是否是同一个对象。self is other
2赞 Bite code 8/5/2009
-1.即使这是两个字典实例,Python 也会自动通过键/值来比较它们。这不是 Java...
0赞 Géry Ogam 6/11/2018
第一种解决方案可以引发 .你必须插入这一行(就像 Python 文档中的这个好例子一样)。AttributeErrorif hasattr(other, "path") and hasattr(other, "title"):
55赞 Christopher 8/4/2009 #3

您可以覆盖对象中的丰富比较运算符

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

评论

4赞 kba 11/19/2013
请注意,在 Python 2.5 及更高版本中,类必须定义 ,但除此之外只需要 、 、 或 之一。由此,Python 可以推断出其他方法。有关详细信息,请参阅 functools__eq__()__lt__()__le__()__gt__()__ge__()
1赞 Arel 5/11/2015
@kba,我不认为这是真的。这可能适用于模块,但不适用于标准比较器:只有在实现该方法时才有效。functoolsMyObj1 != Myobj2__ne__()
6赞 Anentropic 9/8/2017
关于 FuncTools 的具体提示应该是在类上使用装饰器,然后如上所述,您可以只定义另一个,其余的将被派生@functools.total_ordering__eq__
-6赞 asb 8/4/2009 #4

与 == 相比,类的实例不相等。最好的方法是将 cmp 函数交给你的类,该类将执行这些操作。

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

在您的例子中 cmp(doc1,doc2) 如果内容相同,它将返回 -1。

490赞 Bite code 8/4/2009 #5

您应该__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__foobar

如果要对不可变类型进行建模,还应实现数据模型挂钩__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__ 而不是 .Python 2 用户可能还希望实现 __ne__,因为在 Python 2 中不会自动创建合理的不等式默认行为(即反转相等结果)。__eq__

评论

9赞 init_js 1/1/2020
我对(而不是提高)的使用感到好奇。该主题涵盖于此:stackoverflow.com/questions/878943/...return NotImplementedNotImplementedError
3赞 therealak12 5/29/2021
如果您使用的是 python3.7 或更高版本,请使用 dataclasses
0赞 mike rodent 9/21/2021
请注意:“在 3.9 版中更改:在布尔上下文中评估 NotImplemented 已弃用”(继续说在未来的版本中将发出 DeprecationWarning)。所以我想我们现在应该在绝对必要的情况下返回或提出一个(我觉得这是可取的,因为任何对象确实不等同于另一个类的另一个:这表明我们希望这是一种编码错误发现策略,但可能不是一个好的策略)。NotImplementedFalseNotImplementedErrorFalseNotImplementedError
1赞 the.real.gruycho 12/21/2021
不可哈希的对象不能存储在字典中是不正确的。他们可以。例如,可以将列表存储在字典中。不能做的是用列表(或其他不可哈希的对象)标记字典条目。
0赞 sandwood 6/22/2022
有什么方法可以避免这个代码样板,当 eq 运算符是所有成员的 AND 比较时?
-3赞 user2215595 6/11/2013 #6

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

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

评论

0赞 yacc143 12/1/2014
至少在 Python 2.7 中,默认情况下,对象是按标识比较的。这意味着对于CPython,用实际的话来说,他们通过内存地址进行比较。这就是为什么 cmp(o1, o2) 仅在“o1 is o2”时返回 0,并且根据 id(o1) 和 id(o2) 的值始终返回 1 或 -1 的原因
5赞 fievel 2/26/2016 #7

总结一下:

  1. 建议实现而不是 ,除非您运行 python <= 2.0(已在 2.1 中添加)__eq____cmp____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)
    
-1赞 DalyaG 10/25/2017 #8

如果要逐个属性进行比较,并查看是否失败以及失败的位置,可以使用以下列表推导式:

[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 中调试时,您可以将其压缩一行并进入“评估表达式”窗口。

6赞 user1338062 9/23/2019 #9

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

>>> vars(x) == vars(y)
True

从对象的字段中查看 Python 字典

评论

0赞 Ben 6/8/2020
同样有趣的是,虽然 vars 返回一个字典,但 unittest 的 assertDictEqual 似乎不起作用,尽管视觉审查表明它们实际上是相等的。我通过将字典转换为字符串并比较它们来解决这个问题:self.assertEqual(str(vars(tbl0)), str(vars(local_tbl0)))
1赞 IFink 12/10/2020
对于我的情况来说,这是一个很好的解决方案,我无法更改类本身。
1赞 rayepps 10/18/2019 #10

我写了这个,并把它放在我项目的一个模块中。对于它不是一个类的情况,只需计划 ol' dict,这将遍历两个对象并确保test/utils

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

它很大...它不性感......但是哦,Boi确实有效!

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)

您可以通过删除并仅使用普通的 ol' 来清理它,但是当它失败时您收到的消息非常无济于事。_assertassert

2赞 Victor H. De Oliveira Côrtes 11/13/2019 #11

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

评论

2赞 Das_Geek 11/13/2019
编辑您的答案,并在您的代码中添加进一步的解释,解释为什么它与其他十个答案不同。这个问题已经有十年的历史了,已经有一个公认的答案和几个非常高质量的答案。如果没有其他细节,与其他答案相比,您的答案质量要低得多,并且很可能会被否决或删除。
0赞 Shital Shah 1/12/2020 #12

下面(在我有限的测试中)通过对两个对象层次结构进行深入比较来工作。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

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

0赞 tomgtbst 3/9/2020 #13
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赞 Sarath Chandra 6/5/2020 #14

Python 3.7(及更高版本)中,比较对象实例的相等性是一项内置功能。

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

评论

0赞 Sarath Chandra 6/5/2020
Raymond Hettinger 的 2018 PyCon 演示是开始使用 Python 数据类的绝佳方式。
30赞 Julio Cezar Silva 6/17/2020 #15

如果你正在处理一个或多个无法从内部更改的类,有一些通用且简单的方法可以做到这一点,这些方法也不依赖于特定于差异的库:

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

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

pickle是一个非常常见的 Python 对象序列化库,因此实际上能够序列化几乎任何东西。在上面的代码片段中,我将序列化的 from 与 from 的序列化进行比较。与下一种方法不同,这种方法的优点是还可以对自定义类进行类型检查。strab

最大的麻烦是:由于特定的排序和 [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的随机森林分类器中的子类型就是这种情况:。根据具体情况处理这些情况 - 例如,具体来说,我决定将受影响类型的内容替换为为我提供有关实例的代表性信息的方法的内容(在本例中为方法)。为此,倒数第二行变成了<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__()

编辑:为了组织起见,我用上面丑陋的单行本替换了。在这里,是一个非常通用的反射,以适应更晦涩的库(我在看着你,Doc2Vec)return dict_from(obj)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

>>> a = {'foo':[], 'bar':{}}
>>> b = {'bar':{}, 'foo':[]}
>>> pickle.dumps(a) == pickle.dumps(b)
False

但是,如果你愿意,你可以事先使用 Python 的内置方法。sorted

评论

0赞 the.real.gruycho 12/24/2021
如果你把一个字典传递给 base_typed(),那么 in 行将只返回字典的键,而字典中包含的所有实际数据都将丢失。base_items = [base_typed(item) for item in obj]base_typed(obj)
1赞 Prashant Raj 3/11/2021 #16

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

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