TAGS :Viewed: 10 - Published at: a few seconds ago

[ Python comparison operator overloading ]

For my Python 2.7.3 project I have a class called custom_date, which has a property called fixed_date:

from datetime import date
class custom_date():
    def __init__(self, fixed_date):
        self.fixed_date = fixed_date

    def __lt__(self, other):
        return self.fixed_date < other

    #__gt__, __ge__, __le__, __eq__, __ne__ all implemented the same way

My idea is to be able to directly compare custom_date.fixed_date with the builtin date.

Problem

If I compare a custom_date object to a date object, it's fine. However, if I compare a date object to a custom_date, it returns a TypeError

>>> from datetime import date
>>> x = custom_date(date(2013,2,1))
>>> y = date(2013,2,2)
>>> x > y
False
>>> y > x
TypeError: can't compare datetime.date to instance 

Is there any way around this?

Answer 1


Just subclass date to get this functionality. Since a datetime object is immutable, you need to use the __new__ constructor vs __init__:

from datetime import date
class custom_date(date):
    def __new__(cls, year,month,day):
        return date.__new__(cls, year, month,day)

x = custom_date(2013,2,1)
y = date(2013,2,2)

print x<y
print y<x

Prints:

True
False

Since the determinative class for comparisons is the LH class, the class on the left needs to have the correct comparison operators to deal with the comparison with the class on the right. If there are no comparison operators for either class, instances are sorted by identity -- their memory address. Your error is essentially from trying to compare an apple to an orange: identity to a date class.

Note that there used to be a rcmp that was removed in Python 2.1 to deal with such issues. Introduction of the new style classes and rich comparisons have also led to __cmp__ being deprecated.

Answer 2


I think I know why you are running into problems. Check the data model documentation on docs.python.org.

>>> y > x

calls:

y.__gt__(x)

x is just an instance object, not the fixed_date attribute stored in it:

>>> x
<__main__.custom_date instance at 0x020D78C8>
>>> x.fixed_date
datetime.date(2012, 2, 1)

One way to make it work the way you have it set up is to do:

>>> y > x.fixed_date

I think to "fix" this, you would have make all your dates of type custom_date. Somebody else may have a better solution for you, and I'll be watching, because I'm curious as well.

Answer 3


Found a potential solution, in case anyone else is facing the same problem.

From the Python datetime docs:

In other words, date1 < date2 if and only if date1.toordinal() < date2.toordinal(). In order to stop comparison from falling back to the default scheme of comparing object addresses, date comparison normally raises TypeError if the other comparand isn’t also a date object. However, NotImplemented is returned instead if the other comparand has a timetuple() attribute. This hook gives other kinds of date objects a chance at implementing mixed-type comparison. If not, when a date object is compared to an object of a different type, TypeError is raised unless the comparison is == or !=. The latter cases return False or True, respectively.

If x.__op__(y) returns NotImplemented instead of raising a TypeError, Python would automatically attempt the reverse comparison y.__rop__(x) (More info about comparisons here).

date however raises a TypeError if the other object isn't a date object, unless the other object implements the timetuple() attribute.

So the solution is to add a dummy timetuple() method to my class.

from datetime import date
class custom_date():
    def __init__(self, fixed_date):
        self.fixed_date = fixed_date

    def __lt__(self, other):
        return self.fixed_date < other

    #__gt__, __ge__, __le__, __eq__, __ne__ all implemented the same way

    def timetuple():
        pass