Этот вопрос так сложно выразить словами. Надеюсь, что название запечатлеет его правильно.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'
@SheridanVespo: Что делать? Если он вызывает «AttributeError», вы нарушаете [принцип замены Лискова] (http://en.wikipedia.org/wiki/Liskov_substitution_principle), а возврат «Нет», возможно, имеет ту же проблему. – Kevin
Когда я попробовал его в консоли раньше, это не сработало. Когда я попробовал это сейчас, это произошло. Я не знаю, что я сделал неправильно. Простите мой уже удаленный комментарий, вы были правы. – nfs