在Python 3中动态更改__slots__

wrw*_*rwt 5 python slots python-3.x

假设我有一个班级 __slots__

class A:
    __slots__ = ['x']

a = A()
a.x = 1   # works fine
a.y = 1   # AttributeError (as expected)
Run Code Online (Sandbox Code Playgroud)

现在我要改变__slots__A.

A.__slots__.append('y')
print(A.__slots__)   # ['x', 'y']
b = A()
b.x = 1   # OK
b.y = 1   # AttributeError (why?)
Run Code Online (Sandbox Code Playgroud)

b是在改变之后创建__slots__A,所以Python原则上可以为内存分配内存b.y.为什么没有?

如何正确修改__slots__类,以便新实例具有修改后的属性?

Mar*_*ers 13

__slots__创建类后,您无法动态更改属性.那是因为该值用于为每个槽创建特殊描述符.从__slots__文档:

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

你可以在课堂上看到描述符__dict__:

>>> class A:
...     __slots__ = ['x']
... 
>>> A.__dict__
mappingproxy({'__module__': '__main__', '__doc__': None, 'x': <member 'x' of 'A' objects>, '__slots__': ['x']})
>>> A.__dict__['x']
<member 'x' of 'A' objects>
>>> a = A()
>>> A.__dict__['x'].__get__(a, A)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: x
>>> A.__dict__['x'].__set__(a, 'foobar')
>>> A.__dict__['x'].__get__(a, A)
'foobar'
>>> a.x
'foobar'
Run Code Online (Sandbox Code Playgroud)

你不能自己创建这些额外的描述符.即使你可以,你也不能为这个类生成的实例上的额外插槽引用分配更多的内存空间,因为这是存储在类的C结构中的信息,而不是Python代码可以访问的方式.

这都是因为__slots__它只是将构成Python实例的元素的低级处理扩展到Python代码; 常规 Python实例上的__dict____weakref__属性始终实现为插槽:

>>> class Regular: pass
... 
>>> Regular.__dict__['__dict__']
<attribute '__dict__' of 'Regular' objects>
>>> Regular.__dict__['__weakref__']
<attribute '__weakref__' of 'Regular' objects>
>>> r = Regular()
>>> Regular.__dict__['__dict__'].__get__(r, Regular) is r.__dict__
True
Run Code Online (Sandbox Code Playgroud)

所有Python开发人员都在这里扩展系统,使用任意名称添加更多这样的插槽,这些名称取自__slots__正在创建的类的属性,以便您可以节省内存 ; 字典比插槽中的值的简单引用占用更多内存.通过指定__slots__禁用__dict____weakref__插槽,除非您在__slots__序列中明确包含它们.

扩展槽的唯一方法是子类; 您可以使用该type()函数或使用工厂函数动态创建子类:

def extra_slots_subclass(base, *slots):
    class ExtraSlots(base):
        __slots__ = slots
    ExtraSlots.__name__ = base.__name__
    return ExtraSlots
Run Code Online (Sandbox Code Playgroud)


mgi*_*son 3

在我看来,类型变成__slots__元组是它的首要行动顺序之一。然后它将元组存储在扩展类型对象上。因为在这一切之下,蟒蛇正在看着 a tuple,所以没有办法改变它。事实上,我什至不确定您是否可以访问它,除非您首先将元组传递给实例。

您设置的原始对象仍然保留为类型的属性这一事实(也许)只是为了方便内省。

不能修改__slots__并期望它出现在某个地方(实际上 - 从可读性的角度来看,你可能真的不想这样做,对吧?)...

当然,您始终可以子类化来扩展插槽:

>>> class C(A):
...   __slots__ = ['z']
... 
>>> c = C()
>>> c.x = 1
>>> c.z = 1
Run Code Online (Sandbox Code Playgroud)