在Python中如何检查两个对象是否已改变?

2 投票
2 回答
3400 浏览
提问于 2025-04-18 11:58

我有一个对象,它的结构如下:

class Obj1(object):
    def __init__(self, user, password, items=None):
        self._user = user
        self._password = password
        self._items = items

    def add_items(self, item):
        self._items.append(item)

    def has_changed(self, obj2):
        return self != obj2

现在我做了以下操作:

obj1 = Obj1('me', '1234')
obj1.add_item({'name':'george', 'progress':'70'})
#obj2 = obj1 #wont work since they would point to same object
obj2 = copy.copy(obj1)
obj1.add_item({'name':'monica', 'progress':'86'})
print obj2.has_changed(obj1)

令人惊讶的是,这个操作返回了false。有人能告诉我我漏掉了什么吗?

2 个回答

0

这是你代码的一个可运行版本。我添加了需要的copy模块导入,并加上了冒号,让代码变得有效。

import copy

class Obj1(object):
    def __init__(self, user, password, items=None):
        self._user = user
        self._password = password
        self._items = [] if items==None else items

    def add_item(self, item):
        self._items.append(item)

    def has_changed(self, obj2):
        return self != obj2

obj1 = Obj1('me', '1234')
obj1.add_item({'name':'george', 'progress':'70'})
#obj2 = obj1 #wont work since they would point to same object
obj2 = copy.copy(obj1)
obj1.add_item({'name':'monica', 'progress':'86'})
print(obj2.has_changed(obj1))
print(obj1.has_changed(obj1))

这个看起来可以工作,但实际上并不完全如此(见下文)。注意,我又添加了一个测试,以确保在比较时,无论是True还是False都能正常工作……但到目前为止,测试的效果还不够好。

不过,你应该看看@Viktor的回答,因为那里面解释了对象相等性检查(你在类中继承的部分)并不是检查任何属性值是否相等,而只是检查这两个对象是否是同一个。

6

你可以重写对象的 __eq__ 方法。当你比较对象时,实际上是比较它们的身份(也就是说,它们不是同一个对象,所以用 == 比较会返回 False):

用户自定义的类默认有 __eq__()__hash__() 方法;有了这些方法,所有对象之间的比较结果都是不相等的(除了和它们自己比较),而且 x.__hash__() 返回的是 id(x)

这里有个简单的例子:

>>> class A(object):
...     def __init__(self, i):
...         self.i = i
...
>>>
>>> a = A(1)
>>> b = A(1)
>>> c = A(2)
>>> a == b
False
>>> a == c
False

但是如果你重写了比较方法,就能得到你想要的结果:

>>> class B(object):
...     def __init__(self,i):
...         self.i = i
...     def __eq__(self,o):
...         return self.i == o.i
...     def __ne__(self,o):
...         return self.i != o.i
...
>>> d = B(1)
>>> e = B(1)
>>> f = B(2)
>>> d == e
True
>>> d == f
False
>>>

另外,比较目录时会进行“深度比较”(所以你可以直接比较字典):

>>> d1 = {1:2, 3:4}
>>> d2 = {}
>>> d2[1] = 2
>>> d2[3] = 4
>>> d3 = {5:6, 3:4}
>>> d1 == d2
True
>>> d1 == d3
False

还要注意,在实现 丰富比较 方法时,有一些规则[1][2] 需要遵循,例如:

比较运算符之间没有隐含关系。x==y 的真实性并不意味着 x!=y 是假的。因此,在定义 __eq__() 时,也应该定义 __ne__(),这样运算符的行为才会符合预期。

丰富比较方法的参数永远不会被强制转换。

如果一个类重写了 __eq__() 但没有定义 __hash__(),那么它的 __hash__() 会被隐式设置为 None


请求更新:

参数永远不会被强制转换强制转换 在 Python 词汇表中)意味着检查输入参数(在我的例子中是 o)是你的责任,可以尝试:

>>> d == 1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in __eq__
AttributeError: 'int' object has no attribute 'i'

而且不同类的对象之间也可以进行比较:

>>> d == a
True

关于 __hash__ 被设置为 None 的意思是,hash(obj) 会失败:

>>> hash(d)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'B'

而且每个需要哈希的集合也会失败:

>>> set((d,))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'B'

而对于 A 来说是可以正常工作的:

>>> set((a,))
{<__main__.A object at 0x7f8a85fe4668>}

撰写回答