Jatentaki
Jatentaki

Reputation: 13103

python >= operator on classes

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

Answers (1)

BrenBarn
BrenBarn

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

Related Questions