as3rdaccount
as3rdaccount

Reputation: 3941

Checking two objects in Python if they have changed?

I have an object that has the following layout:

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

Now I do the following:

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)

Surprisingly this returns me false. Can someone point me out what I am missing here?

Upvotes: 2

Views: 3586

Answers (2)

Vyktor
Vyktor

Reputation: 20997

You may just override __eq__ method of the object. When you are just comparing objects only identities are compared (they are not the same object, thus == will result in False):

User-defined classes have __eq__() and __hash__() methods by default; with them, all objects compare unequal (except with themselves) and x.__hash__() returns id(x)

Here's a small example:

>>> 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

But if you override comparison you'll get what you need:

>>> 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
>>>

Also comparing directories does "deep comparison" (so you can compare dictionaries directly):

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

Also note that there are some rules[1][2] that you should follow when implementing rich comparison methods, for example:

There are no implied relationships among the comparison operators. The truth of x==y does not imply that x!=y is false. Accordingly, when defining __eq__(), one should also define __ne__() so that the operators will behave as expected.

Arguments to rich comparison methods are never coerced.

A class that overrides __eq__() and does not define __hash__() will have its __hash__() implicitly set to None.


Requested update:

Arguments are never coerced (coercion in python glossary) means that checking input argument (o in my example) is your responsibility, try:

>>> 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'

And it's even possible to compare objects of different classes:

>>> d == a
True

And about __hash__ being set to None means, that hash(obj) fails:

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

And every collection requiring hashing also fails:

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

While it works with A:

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

Upvotes: 6

holdenweb
holdenweb

Reputation: 37023

Here is a working version of your code. I included the required import of the copy module and added colons to make it valid code.

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))

This appears to work but doesn't really (see below). Note that I added a further test to ensure that the comparison works when both True and False ... but the testing so far simply isn't good enough.

However, you should look at @Viktor's answer, since that explains that object equality checks (which you are inheriting in your class) do not check the equality of any attribute values but simply whether the two are the same object.

Upvotes: 0

Related Questions