2

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?

norok2
  • 25,683
  • 4
  • 73
  • 99

2 Answers2

2

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

RomanPerekhrest
  • 88,541
  • 4
  • 65
  • 105
  • 1
    Thanks! I was looking into `__new__` as a possible way to solve this, but the official documentation was just insufficient for me. – norok2 Aug 06 '19 at 15:41
0

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.

MarLei
  • 51
  • 7
  • I am sorry, could you please explain yourself a bit better? I really do not see how this answer my question. – norok2 Aug 06 '19 at 14:37
  • @MarLei : You are correct. Can you please write a clear answer ? In brief, you just need to replace "_SOMETHING" by "__SOMETHING" (add an underscore) and your problem is solved – ma3oun Aug 06 '19 at 14:49
  • @ma3oun can you show this in a [MVCE](https://stackoverflow.com/questions/57378372/how-to-input-check-a-class-property)? I am 100% positive that changing a custom name everywhere (a.k.a. [refactoring](https://en.wikipedia.org/wiki/Code_refactoring)) in any Python code **should not** affect code execution, but if you happen to discover something like this, it should be reported as a bug **immediately**. – norok2 Aug 06 '19 at 14:54
  • The example I had in mind works for object attributes, not class variables. Sorry about the confusion – ma3oun Aug 06 '19 at 15:47