2015-04-28 4 views
1

Этот вопрос так сложно выразить словами. Надеюсь, что название запечатлеет его правильно.Python: атрибут класса, который по умолчанию имеет значение родителя и является коллективным для всей иерархии

Я ищу:

class Parent(): 
    x = "P" 

class ChildA(Parent): 
    x = "A" 

class ChildB(Parent): 
    # not setting x 
    pass 

С этим, следующее должно работать точно так, как показано:

>>> Parent.x 
'P' 
>>> ChildA.x 
'A' 
>>> ChildB.x 
'P' 
>>> ChildB.x = 'B' 
>>> ChildB.x 
'B' 

Ничего особенного до сих пор, но вот где она становится сложнее, потому что значение родительского атрибута должно быть сохранено:

>>> ChildB.x = None 
>>> ChildB.x 
'P' 

Также мне нужно это для работы:

>>> ChildA.get_x_list() 
['A', 'P'] 
>>> ChildB.get_x_list() 
['P'] # 'P' only appears once 

Я много читал о метаклассами недавно, и я полагал, что это было бы лучше всего сделать за счет использования одного:

has__attr = lambda obj, name: hasattr(obj, "_" + obj.__name__ + "__" + name) 
get__attr = lambda obj, name: getattr(obj, "_" + obj.__name__ + "__" + name) 
set__attr = lambda obj, name, value: setattr(obj, "_" + obj.__name__ + "__" + name, value) 
del__attr = lambda obj, name: delattr(obj, "_" + obj.__name__ + "__" + name) 

class Meta(type): 
    the_parent_name = "mParent" 

    def __new__(cls, class_name, bases, attributes): 
     parent_attribute_id = "_" + class_name + "__" + cls.the_parent_name 

     x = attributes.pop("x", None) 
     if x: 
      attributes["_" + class_name + "__x"] = x 

     # build line of inheritance 
     for b in bases: 
      # find a base that has the parent attribute 
      if has__attr(b, cls.the_parent_name): 
       # set the parent attribute on this class 
       attributes[parent_attribute_id] = b 
       break 
     else: 
      # add the parent attribute to this class, making it an inheritance root 
      attributes[parent_attribute_id] = None 
     return super(Meta, cls).__new__(cls, class_name, bases, attributes) 

    def get_x_list(self): 
     ls = [get__attr(self, "x")] if has__attr(self, "x") else [] 
     parent = get__attr(self, self.the_parent_name) 
     if parent: 
      ls.extend(parent.get_x_list()) 
     return ls 

    @property  
    def x(self): 
     if has__attr(self, "x"): 
      return get__attr(self, "x") 
     else: 
      parent = get__attr(self, self.the_parent_name) 
      if parent: 
       return parent.x 
      else: 
       return None 

    @x.setter 
    def x(self, x): 
     if x: 
      set__attr(self, "x", x) 
     else: 
      del__attr(self, "x") 

Хотя это уже работает точно так же, как и ожидалось, мне было интересно ли с помощью метакласса находится сверху, и на самом деле гораздо более простой способ сделать это?

ВАЖНО: Мне нужно, чтобы это было сделано так, чтобы в производных классах не было абсолютно никакого специального материала. x = "Q" и set_x("Q") приемлемы. Это требование, потому что я разрабатываю API, где Parent является частью библиотеки, а производные классы из моих рук.

Бонусный вопрос: есть ли способ изменить имя («x») атрибута в одном месте? Значение: возможно ли создать get_x_list и свойство x через строку? Я полагаю, что-то вдоль линий:

attributes["get_" + attr_name + "_list"] = ... 

Но всякий раз, когда я попробовал это, я получил:

... missing 1 required positional argument: 'self' 

ответ

3

Вам не нужно метакласса для любой из этих функций.

>>> ChildB.x = None 
>>> ChildB.x 
'P' 

Просто измените первую строку del ChildB.x и это будет работать.

>>> ChildA.get_x_list() 
['A', 'P'] 
>>> ChildB.get_x_list() 
['P'] # 'P' only appears once 

Попробуйте это:

(klass.__dict__['x'] for klass in ChildA.__mro__ if 'x' in klass.__dict__) 

Изменить внешний пара скобок в квадратные скобки, если вам нужен список вместо генератора.

+0

@SheridanVespo: Что делать? Если он вызывает «AttributeError», вы нарушаете [принцип замены Лискова] (http://en.wikipedia.org/wiki/Liskov_substitution_principle), а возврат «Нет», возможно, имеет ту же проблему. – Kevin

+0

Когда я попробовал его в консоли раньше, это не сработало. Когда я попробовал это сейчас, это произошло. Я не знаю, что я сделал неправильно. Простите мой уже удаленный комментарий, вы были правы. – nfs