Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Automatic class decoration (or validation) upon derivation


I have a base class from which I derive multiple subclasses.
Each subclass defines class constants, and I wish to enforce certain limitations on them. For example:

class Base(object):
    # define these in your sub-class, and make sure (NOM % DENOM == 0)
    NOMINATOR = None
    DENOMINATOR = None

class Subclass_good(Base):
    NOMINATOR = 6
    DENOMINATOR = 3

class Subclass_bad(Base):
    NOMINATOR = 7
    DENOMINATOR = 5

I want to be able to enforce the rule (NOM % DENOM == 0).
I currently do this with a class decorator:

def nom_denom_validator(cls):
    assert(cls.NOMINATOR % cls.DENOMINATOR == 0)
    return cls

# and decorate each subclass, e.g.:
@nom_denom_validator
class Subclass_another(Base):
    NOMINATOR = 9
    DENOMINATOR = 12

But I don't like the fact that I need to decorate each subclass (I have plenty). I'm interested whether this can be done by some manipulation on the Base class directly.

Any advice?

like image 524
Yonatan Avatar asked Jan 21 '23 21:01

Yonatan


2 Answers

Ok, funny. I was thinking about it for a while, but only after posting the question - specifically when choosing tags, and adding "metaclass" there - did I realize I may have an answer myself.
So, submitted for review and future knowledge, here goes:

class Base_Metaclass(type):
    def __new__(meta, classname, bases, class_dict):
        new_type = type.__new__(meta, classname, bases, class_dict)
        if not (new_type.NOMINATOR % new_type.DENOMINATOR) == 0:
            raise Exception("Invalid subclass created - validation failed")
        return new_type

# have Base and all its descendants be enforced:
class Base(object):
    __metaclass__ = Base_Metaclass
    # I must pass the validation myself, no None's anymore...
    NOMINATOR = 1
    DENOMINATOR = 1

And now all children should be auto-enforced.

like image 199
Yonatan Avatar answered Jan 23 '23 10:01

Yonatan


You can do the checking in the base classes constructor

class Base(object):
    # define these in your sub-class, and make sure (NOM % DENOM == 0)
    NOMINATOR = None
    DENOMINATOR = None
    def __init__(self):
        assert(self.NOMINATOR % self.DENOMINATOR == 0)

When you create an instance of Subclass_bad(), you'll get AssertionError.

like image 22
Utku Zihnioglu Avatar answered Jan 23 '23 10:01

Utku Zihnioglu