Reputation: 13103
I have a question regarding python '>=' behaviour.
I have an old TimeStamp class, which holds (hour, minute) tuple and offers some methods such as __eq__
, __gt__
, __lt__
.
I am refactoring it to also account for day and second, and to store the data as total seconds. Here I implemented __eq__
, __gt__
, __lt__
as well.
However, further in code I am using >= operator for this class and while the old class version is working properly, with the new one I am getting
TypeError: unorderable types: TimeStamp() >= TimeStamp() error.
Code is below:
class TimeStamp(tuple): # OLD, WORKING VERSION
"""TimeStamp, hh:mm tuple supporting comparison and addition"""
__slots__ = ()
def __new__(cls, *args):
if len(args) == 1: # tuple entrance
hour, minute = args[0]
elif len(args) == 2: # hour, minute entrance
hour, minute = args[0], args[1]
else:
raise TypeError('wrong input to TimeStamp')
div, minute = divmod(minute, 60)
hour += div
_, hour = divmod(hour, 24)
return tuple.__new__(cls, (hour, minute))
@property
def abs_min(self):
return self.hour * 60 + self.minute
def __gt__(self, rhs):
return self.abs_min > rhs.abs_min
def __lt__(self, rhs):
return self.abs_min < rhs.abs_min
def __eq__(self, rhs):
return self.abs_min == rhs.abs_min
New version:
class TimeStamp:
def __init__(self, *args):
for argument in args:
if not isinstance(argument, int):
raise TypeError("Can only build TimeStamp from ints, not: " + str(argument))
if len(args) == 1: # init by abs
self.abs = args[0] # put the ELEMENT, not the tuple itself
elif len(args) == 2: # init by hour:minute
hour, minute = args
self.abs = hour * 60 * 60 + minute * 60
elif len(args) == 4: #init by day:hour:minute:second
day, hour, minute, second = args
self.abs = day * 24 * 60 * 60 + hour * 60 * 60 + minute * 60 + second
else:
raise TypeError("wrong data for TimeStamp: " + str(args))
def __eq__(self, other):
if isinstance(other, TimeStamp):
return self.abs == other.abs
else:
raise TypeError("wrong argument for comparison: " + str(other))
def __gt__(self, other):
if isinstance(other, TimeStamp):
return self.abs > other.abs
else:
raise TypeError("wrong argument for comparison: " + str(other))
def __lt__(self, other):
if isinstance(other, TimeStamp):
return self.abs < other.abs
else:
raise TypeError("wrong argument for comparison: " + str(other))
Now for the comparison part:
if args[1] >= self.start:
>>TypeError: unorderable types: TimeStamp() >= TimeStamp()
I have found two fixes: first, to replace my comparison line with
if args[1] > self.start or args[1] == self.start:
or an alternative, to add
def __ge__(self, other):
if isinstance(other, TimeStamp):
return self.abs >= other.abs
else:
raise TypeError("wrong argument for comparison: " + str(other))
to my new class. However, the old one did work with neither of those fixes. It looks to me as if Python stopped deducting that ((a>b) or (a==b)) implies (a>=b). But why did it work before? Does it have something to do with me subclassing tuple?
PS. don't be scared by my __init__
code, which I included for completeness. It's supposed to be overload-like, but I might be doing it in a non-pythonic way (still learning)
Upvotes: 1
Views: 755
Reputation: 251378
The old one worked because it inherited from tuple
, and tuple
provides __ge__
. Your new version does not inherit from tuple
, so it doesn't have a __ge__
method.
Even in your old version, your __gt__
and __lt__
methods were never being called when using >=
(as you can verify by putting print
inside those methods). The underlying tuple.__ge__
was being called instead. However, for your case, the effect is the same, so you didn't notice. That is, given that the "minutes" number is always less than 60, comparing (hours, minutes)
tuples in the usual way is equivalent to comparing 60*hours + minutes
. So I don't think you really need to define the comparison methods at all if you inherit from tuple.
Upvotes: 2