Python:动态类生成:覆盖成员

4 投票
3 回答
2129 浏览
提问于 2025-04-15 16:20

我有一个Python的类层次结构,我想在运行时对它进行扩展。而且这个层次结构中的每个类都有一个静态属性'dict',我想在每个子类中覆盖这个属性。简单来说,它看起来是这样的:

'dict'是一个受保护的成员(公开但前面有下划线)

class A(object):
    _dict = {}

    @classmethod
    def getdict(cls):
        return cls._dict

    @classmethod
    def setval(cls, name, val):
        cls._dict[name] = val

    @classmethod
    def addchild(cls, name):
        return type(name, (cls, ), { '_dict' : {} })

B = A.addchild('B')
A.setval(1, 5)

print A.getdict()
# prints: {1: 5}
# like expected

print B.getdict()
# prints: {}
# like expected

这就像预期的那样工作。现在的问题是:如果我把这个属性声明为私有的,为什么它就不再工作了呢?

现在同样的事情,'dict'是一个私有成员

class C(object):
    __dict = {}

    @classmethod
    def getdict(cls):
        return cls.__dict

    @classmethod
    def setval(cls, name, val):
        cls.__dict[name] = val

    @classmethod
    def addchild(cls, name):
        return type(name, (cls, ), { '__dict' : {} })

D = C.addchild('D')
C.setval(1, 5)

print C.getdict()
# prints: {1: 5}
# like expected

print D.getdict()
# prints: {1: 5}
# why!?

突然间,D这个类(它是C的子类)在'dict'中有和它的父类一样的值!?

有没有人能好心地解释一下,这是什么原因呢?谢谢!

3 个回答

1

在Java或C++中,“protected”和“private”的概念并不适用。在Python中有一些命名规则,但和你想的并不完全一样。

使用__name会进行一些名称的混淆,这样就很难直接访问,因为这个名字被隐藏了。

你的_dict__dict其实只是类级别的属性,所有这个类的实例都会共享这些属性。

2

这里有一段关于“私有”属性的文档章节,您可以查看一下:文档链接。我在您的类定义上做了一些注释,以便让它更清晰:

class C(object):
    __dict = {} # This creates C.__dict__['_C__dict']

    @classmethod
    def getdict(cls):
        return cls.__dict # Uses cls.__dict__['_C__dict'] 

    @classmethod
    def setval(cls, name, val):
        cls.__dict[name] = val # Uses cls.__dict__['_C__dict'] 

    @classmethod
    def addchild(cls, name):
        return type(name, (cls, ), { '__dict' : {} }) # Creates child.__dict__['__dict']

也就是说,所有的子类都有自己的 __dict 属性,但只有一个来自基类的属性会被使用。

3

phild,正如你所知道的,当你在属性名前加上两个下划线__时,Python解释器会自动把这个属性名改成一个特殊的形式,比如从__attribute变成_CLS__attribute,其中CLS是类的名字。

但是,当你写

return type(name, (cls, ), { '__dict' : {} })

时,字典{ '__dict' : {} }里的键名不会被改名。__dict保持不变。

所以,最后D类里会有D._C__dictD.__dict这两个属性:

(Pdb) dir(D)
['_C__dict', '__class__', '__delattr__', '__dict', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'addchild', 'getdict', 'setval']

D._C__dict指的是C类的属性。所以当你执行

C.setval(1, 5)

时,你实际上是在改变D._C__dictC._C__dict,它们是同一个东西。

撰写回答