Python、__slots__、继承和类变量 ==> 属性是只读的 bug

Posted

技术标签:

【中文标题】Python、__slots__、继承和类变量 ==> 属性是只读的 bug【英文标题】:Python, __slots__, inheritance, and class variables ==> attribute is read-only bug 【发布时间】:2011-08-11 02:30:20 【问题描述】:

我有一棵有数十万个节点的大树,我正在使用__slots__ 来减少内存消耗。我刚刚发现了一个非常奇怪的错误并修复了它,但我不明白我看到的行为。

这是一个简化的代码示例:

class NodeBase(object):
    __slots__ = ["name"]
    def __init__(self, name):
        self.name = name

class NodeTypeA(NodeBase):
    name = "Brian"
    __slots__ = ["foo"]

然后我执行以下操作:

>>> node = NodeTypeA("Monty")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in __init__
AttributeError: 'NodeTypeA' object attribute 'name' is read-only

如果未定义NodeTypeA.name,则没有错误(旁注:该属性是错误的,并且没有理由存在)。如果从未定义过NodeTypeA.__slots__,也没有错误,因此它有一个__dict__

我不明白的是:为什么超类中存在类变量会干扰在​​子类的插槽中设置实例变量?

谁能解释为什么这种组合会导致object attribute is read-only 错误?我知道我的示例是人为的,在实际程序中不太可能是故意的,但这并没有使这种行为变得不那么奇怪。

谢谢, 乔纳森

【问题讨论】:

NodeTypeA 正在创建一个类变量name,而不是为NodeBase 中定义的实例变量赋值。这是故意的吗? 一开始并不是故意的——我不小心在我的代码中出现了这个问题,导致了我所询问的错误。但是后来我很好奇为什么代码会这样,所以我故意把它放在我的代码示例中。 【参考方案1】:

一个较小的例子:

class C(object):
    __slots__ = ('x',)
    x = 0

C().x = 1

documentation on slots 在某一时刻声明:

__slots__ 在类级别通过为每个变量名创建描述符(实现描述符)来实现。因此,类属性不能用于为__slots__定义的实例变量设置默认值;否则,类属性将覆盖描述符分配。

__slots__在使用时,槽属性的属性分配需要通过为槽属性创建的描述符。隐藏子类中的描述符会导致 Python 无法找到设置属性所需的例程。不过,Python 仍然可以看到属性存在(因为它找到了隐藏描述符的对象),因此它报告该属性是只读的。

【讨论】:

类似的答案也贴在这里:***.com/questions/820671/… 好点。我没有意识到它简化了——我认为继承是问题的一部分。显然,正如@Santa 指出的那样,这与上一个问题相同。 Python 不会在此处创建只读描述符。发生的情况是实例没有__dict__,而NodeTypeA 中的name = "Brian" 字符串隐藏了为NodeBase 中的name 槽创建的槽描述符。 Python 看到该实例具有 name 属性(因为属性查找找到了 "Brian" 字符串),但该属性不可分配(因为 "Brian" 没有 __set__ 方法并且实例没有有一个__dict__),所以它报告该属性是只读的。 如果你想看代码,PyObject_GenericSetAttr 是涉及到的__setattr__ 实现。 您还可以通过手动检查类型字典来看到不涉及只读描述符。对于NodeTypeANodeTypeA.__dict__['name'] 是常规字符串,而不是描述符(对于您的C 类,C.__dict__['x']0)。对于NodeBaseNodeBase.__dict__['name']是一个支持读、写和删除的描述符,你可以通过手动调用__get____set____delete__看到。

以上是关于Python、__slots__、继承和类变量 ==> 属性是只读的 bug的主要内容,如果未能解决你的问题,请参考以下文章

__slot__

面对对象之特殊变量__slot__ | Python

python----03(面向对象进阶02)

Python学习---面向对象高级编程

不能从定义 __slots__ 的多个类继承?

python面向对象__slots__变量的运用