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


当前回答

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

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

其他回答

总结如下:

It's advised to implement __eq__ rather than __cmp__, except if you run python <= 2.0 (__eq__ has been added in 2.1) Don't forget to also implement __ne__ (should be something like return not self.__eq__(other) or return not self == other except very special case) Don`t forget that the operator must be implemented in each custom class you want to compare (see example below). If you want to compare with object that can be None, you must implement it. The interpreter cannot guess it ... (see example below) 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)

在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

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

如果==操作符在默认情况下不适用,您总是可以为对象重新定义__cmp__函数。

编辑:

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

如果你正在处理一个或多个不能从内部修改的类,有一些通用的简单方法可以做到这一点,而且不依赖于特定于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内置的排序方法。

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

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