Wednesday, April 22, 2009

More about the __cmp__ method in Python

I just stumbled upon this little something about overloading the __cmp__ method in a Python class. If you are not careful, you will end up having a class whose instances can not be compared to None (as I always do to see if some optional parameter has been passed to a method or function). Lets take a small example:
class Foo(object):

def __init__(self, x):
self._x = x
def __cmp__(self, other):
if type(other) == Foo:
return cmp(self._x, other._x)
else:
raise TypeError("Foo cannot be compared to %s"%str(type(other))) f1 = Foo(1)
f2 = Foo(2)
print 'f1 is smaller than f2:', f1 < f2 # True
print 'f1 is larger than f2:', f1 > f2 # False
print 'f1 is equal to f2:', f1 == f2 # False
print 'f1 is None:', f1 == None # Crash!

This code crashes in the final statement, because the comparison to None calls the __cmp__ method, and it has no way of comparing to None. This could of course be fixed by adding "if other == None: return -1" but the right way to fix this would be by overloading another method called __eq__ that is used to test for object equality.
class Foo(object):

def __init__(self, x):
self._x = x
def __eq__(self, other):
if type(other) == Foo:
return cmp(self, other) == 0
else:
return False
def __cmp__(self, other):
if type(other) == Foo:
return cmp(self._x, other._x)
else:
raise TypeError("Foo cannot be compared to %s"%str(type(other)))

f1 = Foo(1)
f2 = Foo(2)
print 'f1 is smaller than f2:', f1 < f2 # True print 'f1 is larger than f2:', f1 > f2 # False
print 'f1 is equal to f2:', f1 == f2 # False
print 'f1 is None:', f1 == None # False

Now the comparison to None (or in fact to any object that is not of type Foo) is caught by the __eq__ method, and comparisons to other type Foo objects are correctly forwarded to the __cmp__ method.

4 comments:

  1. Hey man,
    thanks a lot! I just stumbeled over this compare to None problem and thought about a nice solution.

    You saved me quite some time, and your solution is pretty elegant, too.

    ReplyDelete
  2. def __cmp__(self, other):
    if type(other) == Foo:
    return cmp(self._x, other._x)
    else:
    raise TypeError("Second argument isn't Foo class")

    What about else in __cmp__?

    ReplyDelete
  3. You are right. Otherwise the behavior when using < and > is undefined when types do not match. Thanks.

    ReplyDelete