[ 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