根据对象实例的属性比较它们是否相等

2025-01-06 08:32:00
admin
原创
96
摘要:问题描述:我有一个类MyClass,它包含两个成员变量foo和bar:class MyClass: def __init__(self, foo, bar): self.foo = foo self.bar = bar 我有该类的两个实例,每个实例都有相同的foo和值b...

问题描述:

我有一个类MyClass,它包含两个成员变量foobar

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


解决方案 1:

您应该实施该方法__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__而不是__eq__。Python 2 用户可能还需要实现__ne__,因为 Python 2 中不会自动创建不等式的合理默认行为(即反转相等结果)。

解决方案 2:

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

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

解决方案 3:

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

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

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

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

最大的麻烦:由于特定的排序和 [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__()

编辑:为了组织,我将上面丑陋的一行替换为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

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

解决方案 4:

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

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

解决方案 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__

解决方案 6:

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

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

从对象的字段查看Python 字典

解决方案 7:

总结一下:

  1. 建议实现__eq__而不是__cmp__,除非你运行 python <= 2.0(__eq__已在 2.1 中添加)

  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)

解决方案 8:

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

  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:

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

@Bite code 的答案是正确的。但我不得不做一些细微的改变来处理None其他类型的值和对象。在我这样做之前,None对象被视为平等的。

from dataclasses import dataclass                                               
                                                                                
                                                                                
@dataclass                                                                      
class GpsPointRaw:                                                                        
    lat: float                                                                  
    lon: float                                                                  
                                                                                          
    def __eq__(self, other) -> bool:                                            
        if not isinstance(other, GpsPointRaw):                                  
            return False                                                        
        if other is None:                                                       
            return False                                                        
        return self.lat == other.lat and self.lon == other.lon

解决方案 11:

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

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

编辑:

正如所指出的,该__cmp__功能自 3.0 起已弃用。相反,您应该使用“丰富比较”方法。

解决方案 12:

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

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

解决方案 13:

下面的方法(在我有限的测试中)通过对两个对象层次结构进行深度比较来实现。它处理各种情况,包括对象本身或其属性是字典的情况。

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

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

解决方案 14:

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

解决方案 15:

如果您想要进行逐个属性的比较,并查看是否失败以及在何处失败,您可以使用以下列表推导:

[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 中调试时输入“评估表达式”窗口。

解决方案 16:

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

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

解决方案 17:

类的实例与 == 进行比较时不相等。最好的方法是将cmp函数赋值给将执行此操作的类。

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

在您的情况下,cmp(doc1,doc2) 如果内容相同,它将返回 -1。

相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   1590  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1361  
  信创产品在政府采购中的占比分析随着信息技术的飞速发展以及国家对信息安全重视程度的不断提高,信创产业应运而生并迅速崛起。信创,即信息技术应用创新,旨在实现信息技术领域的自主可控,减少对国外技术的依赖,保障国家信息安全。政府采购作为推动信创产业发展的重要力量,其对信创产品的采购占比情况备受关注。这不仅关系到信创产业的发展前...
信创和国产化的区别   18  
  信创,即信息技术应用创新产业,旨在实现信息技术领域的自主可控,摆脱对国外技术的依赖。近年来,国货国用信创发展势头迅猛,在诸多领域取得了显著成果。这一发展趋势对科技创新产生了深远的推动作用,不仅提升了我国在信息技术领域的自主创新能力,还为经济社会的数字化转型提供了坚实支撑。信创推动核心技术突破信创产业的发展促使企业和科研...
信创工作   18  
  信创技术,即信息技术应用创新产业,旨在实现信息技术领域的自主可控与安全可靠。近年来,信创技术发展迅猛,对中小企业产生了深远的影响,带来了诸多不可忽视的价值。在数字化转型的浪潮中,中小企业面临着激烈的市场竞争和复杂多变的环境,信创技术的出现为它们提供了新的发展机遇和支撑。信创技术对中小企业的影响技术架构变革信创技术促使中...
信创国产化   19  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用