Унаследованные, но не разделяемые (статические) члены класса в Python

#python #oop

#python #ооп

Вопрос:

Я пишу небольшой API проверки и инициализирую ограничения для каждого поля в __init__ методе класса. Но эта настройка в принципе может быть выполнена для класса один раз.

 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())
  

Но проблема в том, что ограничения для каждого поля должны где-то храниться, и структура данных для этого является частью унаследованного класса Validatable . Таким образом, все производные классы использовали бы одну и ту же структуру данных, если бы ограничения были установлены на уровне класса, чего не должно произойти!

 class Demo(Validatable):

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

    def __init__(self):
        self.count = 11
  

Есть ли возможность наследовать структуру данных и инициализировать ее на уровне класса в производном классе без совместного использования структуры данных для ограничений?

Комментарии:

1. Является constraint_field() методом класса или статическим методом? Как это определяется?

2. На данный момент это обычный метод (instance), но я хочу это изменить. По сути, метод добавляет ограничения к dict с именем поля в качестве ключа и списком ограничений в качестве значения.

Ответ №1:

Этот вопрос состоит из двух частей.

  1. Как установить значения Validatable структур данных на уровне подкласса, а не в унаследованном Validatable классе; и
  2. Как определить constrain_field метод, чтобы его можно было вызывать один раз при инициализации класса, а не каждый раз при создании экземпляра.

Что касается (1), инициализатор Validatable класса может получить доступ к классу экземпляра, используя его __class__ свойство. Например:

 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
  

Этот код выводит:

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

Затем можно было бы определить constrain_field метод для использования __class__ свойства экземпляра, с которым он вызывается, для настройки необходимых структур данных.

К сожалению, все это требует, чтобы экземпляр класса был создан до того, как можно будет настроить структуры данных, и это также означает, что constrain_field метод вызывается каждый раз, когда создается экземпляр. Очевидно, что было бы предпочтительнее сделать это при инициализации класса, которая является частью (2) вопроса.

Для решения части (2) я бы рекомендовал использовать декораторы python. Рассмотрим следующий код, который объединяет функцию свойства Python (используемую в качестве декоратора) с пользовательской функцией декоратора, вызываемой 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)
  

Он печатает:

 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
  

При использовании декораторов таким образом, вся инициализация выполняется один раз во время определения класса.