brain storm
brain storm

Reputation: 31252

how to compare for equality for None objects in custom class in python?

I am writing a Queue data structure for python purely for learning purposes. here is my class. when I compare two Queue object for equality, I get error. I think the error pops up, because I dont compare for None in my __eq__ .but how can I check for None and return accordinly. in fact, I am using list under the hood and calling its __eq__, thinking it should take care as shown here, but it does not

>>> l=[1,2,3]
>>> l2=None
>>> l==l2
False

Here is my class:

@functools.total_ordering
class Queue(Abstractstruc,Iterator):

    def __init__(self,value=[],**kwargs):
        objecttype = kwargs.get("objecttype",object)
        self.container=[]
        self.__klass=objecttype().__class__.__name__
        self.concat(value)


    def add(self, data):
        if (data.__class__.__name__==self.__klass or self.__klass=="object"): 
            self.container.append(data)
        else:
            raise Exception("wrong type being added")

    def __add__(self,other):
        return Queue(self.container + other.container)


    def __iadd__(self,other):
        for i in other.container:
            self.add(i)
        return self


    def  remove(self):
        return self.container.pop(0)


    def peek(self):
        return self.container[0]


    def __getitem__(self,index):
        return self.container[index]


    def __iter__(self):
        return Iterator(self.container)

    def concat(self,value):
        for i in value:
            self.add(i)

    def __bool__(self):
        return len(self.container)>0

    def __len__(self):
        return len(self.container)


    def __deepcopy__(self,memo):
        return Queue(copy.deepcopy(self.container,memo))

    def __lt__(self,other):
        return self.container.__lt__(other.container)

    def __eq__(self, other):
        return self.container.__eq__(other.container)

But when I try compare using the above class I get:

>>> from queue import Queue
>>> q = Queue([1,2,3])
>>> q
>>> print q
<Queue: [1, 2, 3]>
>>> q1 = None
>>> q==q1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "queue.py", line 65, in __eq__
    return self.container.__eq__(other.container)
AttributeError: 'NoneType' object has no attribute 'container'
>>> 

Upvotes: 3

Views: 4245

Answers (3)

lvc
lvc

Reputation: 35069

Tell Python that you don't know how to compare against other types:

def __eq__(self, other):
    if not isinstance(other, Queue):
        return NotImplemented
    return self.container.__eq__(other.container)

you might consider checking hasattr(other, 'container') instead of the isinstance, or catch the AttributeError.

But the important thing is that, unlike other answers recommend, you do not want to return False when other isn't a Queue. If you return NotImplemented, Python will give other a chance to check the equality; if you return False, it won't. Differentiate between the three possible answers to the question "are these objects equal": yes, no, and I don't know.

You'll want to do something similar in your __lt__, where the difference is even more apparent: if you return False from both __lt__ and __eq__, then the __gt__ inserted by total_ordering will return True - even though you can't do the comparison. If you return NotImplemented from both of them, it will also be NotImplemented.

Upvotes: 3

user764357
user764357

Reputation:

Your problem is how you are implementing __eq__.

Look at this code:

q = Queue([1,2,3])
q1 = None
q==q1

And lets rewrite it as the equivilent:

q = Queue([1,2,3])
q == None

Now, in Queue.__eq__ we have:

def __eq__(self, other):
    return self.container.__eq__(other.container)

But other is None, which means the return statement is calling:

self.container.__eq__(None.container)

As your error rightly states:

'NoneType' object has no attribute 'container'

Because it doesn't! None doesn't have a container attribute.

So, the way to do it, depends on how you want to treat it. Now, obviously, a Queue object can't be None if its been defined, so:

return other is not None and self.container.__eq__(other.container)

Will lazily evaluate if other is None, and return False before evalauting the part of the expression after the and. Otherwise, it will perform the evaulation. However, you will get other issues if other is not of type Queue (or more correctly the other object doesn't have a container attribute), such as:

q = Queue([1,2,3])
q == 1
>>> AttributeError: 'int' object has no attribute 'container'

So... depending on your logic, and if a Queue can't be "equal" to other types (which is something only you can say), you can check for the correct type like so:

return other is not None and type(self) == type(other) and self.container.__eq__(other.container)

But... None is a NoneType, so it can never be of the same type as a Queue. So we can shorten it again to just:

return type(self) == type(other) and self.container.__eq__(other.container)

edit: As per mglisons comments:

This could be made more pythonic by using the regular equality statement:

return type(self) == type(other) and self.container == other.container

They have also raised a good point regarding the use of type in checking eaulity. If you are certain that Queue would never be subclassed (which is difficult to state). You could use exception handling to capture the AttributeError effectively, like so:

def __eq__(self, other):
    try:
        return self.container == other.container
    except AttributeError:
        return False    # There is no 'container' attribute, so can't be equal
    except:
        raise           # Another error occured, better pay it forward

The above may be considered a little overengineered, but is probably one of the better ways to approach this from a safety and resuability perspective.

Or a better, shorter approach (which I should have thought of initially) using hasattr is:

return hasattr(other, 'container') and self.container == other.container

Upvotes: 4

Sam Hartman
Sam Hartman

Reputation: 6489

you can do something like

    def __eq__(self,other):
        if other is None: return False
        return self.container.__eq__(other.container)

You may also want to do something like

if not isinstance(other,Queue): return False

Upvotes: 1

Related Questions