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


当前回答

如果你想逐个属性进行比较,并查看它是否以及在哪里失败,你可以使用下面的列表推导式:

[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”窗口中输入。

其他回答

如果你想逐个属性进行比较,并查看它是否以及在哪里失败,你可以使用下面的列表推导式:

[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”窗口中输入。

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

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

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

pickle是Python对象的一个非常常见的序列化库,因此能够序列化几乎任何东西,真的。在上面的代码片段中,我比较了来自序列化a的str和来自b的str。与下一个方法不同的是,这个方法还具有类型检查自定义类的优点。

最大的麻烦是:由于特定的排序和[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__对我来说不是一个选择。

涵盖所有基础

You may be in a scenario where one or more of the custom classes being compared do not have a __dict__ implementation. That's not common by any means, but it is the case of a subtype within sklearn's Random Forest classifier: <type 'sklearn.tree._tree.Tree'>. Treat these situations on a case by case basis - e.g. specifically, I decided to replace the content of the afflicted type with the content of a method that gives me representative information on the instance (in this case, the __getstate__ method). For such, the second-to-last row in base_typed became

d = obj if T is dict else obj.__dict__ if '__dict__' in dir(obj) else obj.__getstate__()

编辑:为了便于组织,我用return dict_from(obj)替换了上面丑陋的一行代码。在这里,dict_from是一个真正通用的反射,用于容纳更模糊的库(我正在看您,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):
        # 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内置的排序方法。

在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

重写对象中的富比较运算符。

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

根据具体情况,你可以这样做:

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

参见对象字段中的Python字典