How to input-check a class property

214 Views Asked by At

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?

2

There are 2 best solutions below

1
RomanPerekhrest On BEST ANSWER

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

4
MarLei On

First of all. Use two underlines to save the propertie access from outside. Second: If you inherit from the base class the property is still available. No need for new defining it.