Search code examples
pythonclassclass-properties

How to input-check a class property


I have a certain class MyClass with a class attribute SOMETHING:

class MyClass(object):
    SOMETHING = 12

I would like SOMETHING to be enforced to meet a certain condition, e.g. SOMETHING < 100. How could I do that?

Ultimately, this class is meant to be subclassed by the user, e.g.:

class MySubClass(MyClass):
    SOMETHING = 13

I have been looking into the class-property approaches outlined here, but they all seem to not play nicely with defining the property in the class body.

For example:

class MyClassMeta(type):
    _SOMETHING = 0

    @property
    def SOMETHING(cls):
        return cls._SOMETHING

    @SOMETHING.setter
    def SOMETHING(cls, something):
        if something < 100:
            cls._SOMETHING = something
        else:
            raise ValueError('SOMETHING must be < 100')


class MyClass(object, metaclass=MyClassMeta):
    # this is ignored
    SOMETHING = 100


x = MyClass()
print(MyClass.SOMETHING)
# 0
print(type(x).SOMETHING)
# 0

However, if the attribute is accessed normally, e.g.:

MyClass.SOMETHING = 50
print(MyClass.SOMETHING)
# 50
print(type(x).SOMETHING)
# 50

it works fine, even with subclassing:

class MySubClass(MyClass):
    SOMETHING = 40


y = MySubClass()
print(MySubClass.SOMETHING)
# 50
print(type(y).SOMETHING)
# 50

except that setting SOMETHING in the subclass is also ignored.

So, how could I trigger the execution of the @SOMETHING.setter code when SOMETHING is defined in the body of the class?


Solution

  • Use the following simple metaclass implementation to validate class specific attributes on declaration phase:

    class MyClassMeta(type):
        def __new__(cls, clsname, bases, clsdict):
            if clsdict['SOMETHING'] >= 100:  # validate specific attribute value
                raise ValueError(f'{clsname}.SOMETHING should be less than 100')
            return type.__new__(cls, clsname, bases, clsdict)
    
    
    class MyClass(metaclass=MyClassMeta):
        SOMETHING = 12
    
    
    class MyChildClass(MyClass):
        SOMETHING = 100
    

    Running throws:

    ValueError: MyChildClass.SOMETHING should be less than 100
    

    https://docs.python.org/3/reference/datamodel.html#basic-customization