deamon
deamon

Reputation: 92397

Inherited but not shared (static) class members in Python

I'm writing a little validation API and initialize the constraints for each field in the class' __init__ method. But this setup could principially be done for the class a single time.

class Demo(Validatable):

    def __init__(self):
        Validatable.__init__(self)
        self.count = 11

        # could be done at class level
        self.constrain_field("count", Max(9), Even())

But the problem is, that the constraints for each field have to be stored somewhere and the data structure to do that is part of the inherited class Validatable. So all derived classes would share the same data structure, if the constraints were set at class level, what should not happen!

class Demo(Validatable):

    # doesn't work!
    Validatable.constrain_field("count", Max(9), Even())

    def __init__(self):
        self.count = 11

Is there a possibility to inherit the data structure and initialize it at class level in the derived class without sharing the data structure for the constraints?

Upvotes: 2

Views: 236

Answers (1)

srgerg
srgerg

Reputation: 19329

There are two parts to this question.

  1. How does one set the values of the Validatable data structures at the subclass level, instead of in the inherited Validatable class; and
  2. How does one define the constrain_field method so that it can be called once on class initialization, instead of every time an instance is created.

With respect to (1), the initializer of the Validatable class can access the class of the instance using its __class__ property. For example:

class Validatable(object):
    def __init__(self):
        self.__class__.fieldName = "value for " + self.__class__.__name__

class Demo(Validatable):
    def __init__(self):
        super(Demo, self).__init__()

class Demo2(Validatable):
    def __init__(self):
        super(Demo2, self).__init__()

d = Demo()
d2 = Demo2()
print "Demo.fieldName = " + Demo.fieldName
print "Demo2.fieldName = " + Demo2.fieldName

This codes prints:

Demo.fieldName = value for Demo
Demo2.fieldName = value for Demo2

The constrain_field method could then be defined to use the __class__ property of the instance it is called with to set up the necessary data structures.

Unfortunately this all requires that an instance of the class be created before the data structures can be set up and it also means that the constrain_field method is called every time an instance is created. Clearly it would be preferable to do this on class initialization, which is part (2) of the question.

To solve part (2), I would recommend using python decorators. Consider the following code which combines the Python property function (used as a decorator) with a custom decorator function called constrain_field:

def Max(maxValue):
    def checkMax(value):
        return value <= maxValue
    checkMax.__doc__ = "Value must be less than or equal to " + str(maxValue)
    return checkMax

def Even():
    def checkEven(value):
        "Value must be even"
        return value%2 == 0
    return checkEven

def constrain_field(*constraints):
    def constraint_decorator(setter):
        def checkConstraints(self, value):
            ok = True
            for c in constraints:
                if not c(value):
                    ok = False
                    print "Constraint breached: " + c.__doc__
            if ok:
                setter(self, value)
        return checkConstraints
    return constraint_decorator

class Demo(object):
    def __init__(self):
        self._count = 2

    @property
    def count(self):
        return self._count

    @count.setter
    @constrain_field(Max(9), Even())
    def count(self, value):
        self._count = value

d = Demo()
print "Setting to 8"
d.count = 8
print "Setting to 9"
d.count = 9
print "Setting to 10"
d.count = 10
print "Count is now " + str(d.count)

It prints:

Setting to 8
Setting to 9
Constraint breached: Value must be even
Setting to 10
Constraint breached: Value must be less than or equal to 9
Count is now 8

By using decorators in this fashion, all the initialization is done once during the definition of the class.

Upvotes: 2

Related Questions