Reid Ballard
Reid Ballard

Reputation: 1525

Comparison Operators for Class Objects (not instances)

In my current project I found it clearer and more logical to declare Subclasses (similar to Django's Model syntax) rather than creating instances of the base object. On the premise of keeping it simple to read, I also wanted to add comparators to the parent class. Here is a simple example to clarify the situation:

from functools import total_ordering

@total_ordering
class Version():
    VERSIONNUMBER = None

    def __eq__(self, other):
        try: ## catch issubclass(instance, Version)
            if issubclass(other, Version): return self.VERSIONNUMBER == other.VERSIONNUMBER
        except TypeError: pass
        return NotImplemented

    def __lt__(self, other):
        try:
            if issubclass(other, Version): return self.VERSIONNUMBER < other.VERSIONNUMBER
        except TypeError: pass
        return NotImplemented

class Version1(Version):
    VERSIONNUMBER = 1.0

class Version2(Version):
    VERSIONNUMBER = 2.0

assert Version2 > Version1

This, however, throws TypeError: '>' not supported between instances of 'type' and 'type'.

Things I've tried

Upon reflection, I understand this means that the rich comparison methods are being called from type (which object/class is an instance of), so a simple fix like adding the classmethod decorator to the comparison methods doesn't do anything. Making an Orderable super class is obviously out as well since that would simply be another instance of type and therefore type's comparitors would still be called. I don't think this is something that could be implemented with a metaclass, but I'm not fluent enough in them to say that with absolute certainty.

My searches generally returned irrelevant topics:

For the time being I can work around it in code by writing Version2.VERSIONNUMBER > Version1.VERSIONNUMBER, but I'm really curious how it could be implemented (since it has been my experience that you can do just about anything in Python). My assumption is that the class would have to be constructed using an object subtype, I'm just not sure where to begin with that.

Upvotes: 1

Views: 313

Answers (1)

Mark
Mark

Reputation: 92440

Caveat: Off the top of my head, I'm not sure if you will be able to get total_ordering to play well here.

But one method to get the comparison methods to work on a class is to use a meta class rather inheriting them from a parent. For example:

class Version_Meta(type):
    def __eq__(self, other):
        try:
            return self.VERSIONNUMBER == other.VERSIONNUMBER
        except TypeError: pass
        return NotImplemented

    def __lt__(self,other):
        try:
            return self.VERSIONNUMBER < other.VERSIONNUMBER
        except TypeError: pass
        return NotImplemented

class Version(metaclass=Version_Meta):
    VERSIONNUMBER = None
    
class Version1(Version):
    VERSIONNUMBER = 1.0

class Version2(Version):
    VERSIONNUMBER = 2.0

Version1 < Version2
# True

Version2 < Version1
# False

Upvotes: 1

Related Questions