【问题标题】:Python: Class attribute which defaults to parent's value and is collectible for entire hierarchyPython:类属性,默认为父级的值,可用于整个层次结构
【发布时间】:2015-04-28 15:30:08
【问题描述】:

这个问题很难用语言表达。我希望标题能正确捕捉到它。

我在寻找什么:

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_listx 属性?我想像这样的事情:

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

但每当我尝试这样做时,我得到了:

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

【问题讨论】:

    标签: python metaclass class-attributes


    【解决方案1】:

    这些功能中的任何一个都不需要元类。

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

    如果您需要列表而不是生成器,请将最外面的一对括号更改为方括号。

    【讨论】:

    • @SheridanVespo:它应该怎么做?如果它引发了AttributeError,那么您就违反了Liskov substitution principle,并且返回None 可以说有同样的问题。
    • 我之前在控制台试的时候,还是不行。当我刚才尝试它时,它确实如此。我不知道我做错了什么。原谅我已经删除的评论,你是对的。
    猜你喜欢
    • 1970-01-01
    • 2019-02-28
    • 2020-09-30
    • 2021-07-05
    • 1970-01-01
    • 1970-01-01
    • 2011-04-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多