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__
实现。
您还可以通过手动检查类型字典来看到不涉及只读描述符。对于NodeTypeA
,NodeTypeA.__dict__['name']
是常规字符串,而不是描述符(对于您的C
类,C.__dict__['x']
是0
)。对于NodeBase
,NodeBase.__dict__['name']
是一个支持读、写和删除的描述符,你可以通过手动调用__get__
、__set__
和__delete__
看到。以上是关于Python、__slots__、继承和类变量 ==> 属性是只读的 bug的主要内容,如果未能解决你的问题,请参考以下文章