具有自定义元类的类的所有子类共享相同的属性,即使它们不应该

Posted

技术标签:

【中文标题】具有自定义元类的类的所有子类共享相同的属性,即使它们不应该【英文标题】:All subclasses of a class with a custom metaclass share the same attributes even though they shouldn't 【发布时间】:2020-01-09 12:25:34 【问题描述】:

我定义了一个元类MyMeta 和一个名为MyAttributeType 的自定义属性。然后我创建了4个类,第一个类MyObjectA的元类属性设置为MyMeta,类MyObjectB继承自MyObjectA并有一些属性,然后MyObjectCMyObjectD都继承自@ 987654329@,它们都有一些额外的属性。

在元类中,我将这些属性添加到列表中,因此我希望该列表仅包含类本身及其父类的属性,而不是它们都获取所有属性。

我认为看代码更容易,下面是一个最小的测试,你可以复制并运行它,应该可以工作。远低于预期的输出。

class MyAttributeType(object):

    def __init__(self, name=None, node=None):
        self._name = name
        self._node = node

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, value):
        self._name = value

    @property
    def node(self):
        return self._node

    @node.setter
    def node(self, value):
        self._node = value


class MyMeta(type):

    def __new__(mcs, clsname, clsbases, clsattributes):
        result_dict = "_attributes": []

        for base in clsbases:
            try:
                for key, value in base.__dict__.items():
                    if isinstance(value, property) or isinstance(value, MyAttributeType):
                        result_dict[key] = value
                result_dict["_attributes"] = base.__dict__["_attributes"]
            except KeyError:
                pass

        for key, value in clsattributes.items():
            if isinstance(value, MyAttributeType):

                if key.startswith("_"):
                     key = key[1:]

                result_dict["_0".format(key)] = value
                result_dict["_attributes"].append(value)

                value.name = key

                result_dict[key] = value

            else:
                result_dict[key] = value

        inst = super(MyMeta, mcs).__new__(mcs, clsname, clsbases, result_dict)
        return inst


class MyObjectA(object):

    __metaclass__ = MyMeta

    _name = None
    _attributes = []

    def __init__(self, name):
        self._name = name

        for attr in self._attributes:
            attr.node = self._name

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, value):
        self._name = value

    @property
    def attributes(self):
        return [attr.name for attr in self._attributes]


class MyObjectB(MyObjectA):

    attr1 = MyAttributeType()
    attr2 = MyAttributeType()
    attr3 = MyAttributeType()


class MyObjectC(MyObjectB):

    attr4 = MyAttributeType()
    attr5 = MyAttributeType()
    attr6 = MyAttributeType()


class MyObjectD(MyObjectB):

    attr7 = MyAttributeType()
    attr8 = MyAttributeType()
    attr9 = MyAttributeType()


a = MyObjectA("testA")
b = MyObjectB("testB")
c = MyObjectC("testC")
d = MyObjectD("testD")

print a.attributes
print b.attributes
print c.attributes
print d.attributes

输出:

['attr2', 'attr3', 'attr1', 'attr6', 'attr5', 'attr4', 'attr7', 'attr8', 'attr9']
['attr2', 'attr3', 'attr1', 'attr6', 'attr5', 'attr4', 'attr7', 'attr8', 'attr9']
['attr2', 'attr3', 'attr1', 'attr6', 'attr5', 'attr4', 'attr7', 'attr8', 'attr9']
['attr2', 'attr3', 'attr1', 'attr6', 'attr5', 'attr4', 'attr7', 'attr8', 'attr9']

预期:

[]
['attr2', 'attr3', 'attr1']
['attr2', 'attr3', 'attr1', 'attr6', 'attr5', 'attr4']
['attr2', 'attr3', 'attr1', 'attr7', 'attr8', 'attr9']

【问题讨论】:

没有必要明确指出您的版本,因为您的出版物表明了您上一版本的日期和时间,并且有它的历史。 “下面是一个最小的测试”恐怕不是最小的。从您的输出来看,两个类就足够了,名称字段、属性和赋值完全是多余的,而且大多数元类__new__ 也不需要。 FWIF,@​​987654334@ 确保您在所有实例之间都有一个共享列表 - 您是否认为这是必要的而不是问题的原因? 我添加它的原因是没有我没有得到父级的属性,而只有类本身的属性,但是既然你把它标记出来了,我会更多地研究它 您应该保留一份副本,而不是原来的清单。 【参考方案1】:

您需要将基类的_attributes 共享为副本

            result_dict["_attributes"] = base.__dict__["_attributes"][:]

共享列表而不复制它意味着更改在所有引用上都是可见的。您目前正在执行与此等效的操作:

>>> base_attributes = [] 
>>> child_attributes = base_attributes
>>> child_attributes.append(2)
>>> base_attributes
[2]

使用[:] 创建分配时所有元素的副本:

>>> base_attributes = [] 
>>> child_attributes = base_attributes[:]
>>> child_attributes.append(2)
>>> base_attributes
[]

【讨论】:

以上是关于具有自定义元类的类的所有子类共享相同的属性,即使它们不应该的主要内容,如果未能解决你的问题,请参考以下文章

为啥元类不能访问由元类定义的类的子类继承的属性?

3.1.18 元类的练习

浅谈元类(个人理解)

python(18):类(2)

说说 Python 的继承

为啥一个类具有它的元类的属性?