假设我有一个包含__slots__的类
class A:
__slots__ = ['x']
a = A()
a.x = 1 # works fine
a.y = 1 # AttributeError (as expected)现在我要更改A的__slots__。
A.__slots__.append('y')
print(A.__slots__) # ['x', 'y']
b = A()
b.x = 1 # OK
b.y = 1 # AttributeError (why?)b是在A的__slots__发生变化后创建的,所以原则上,Python可以为b.y分配内存。为什么没有呢?
如何正确修改类的__slots__,让新的实例拥有修改后的属性?
发布于 2015-01-13 01:52:22
您不能在创建类后动态更改__slots__属性。这是因为该值用于为每个插槽创建特殊的。从__slots__ documentation
通过为每个变量名创建描述符(实现描述符),可以在类级别实现
__slots__。因此,不能使用类属性为__slots__定义的实例变量设置默认值;否则,类属性将覆盖描述符分配。
您可以在类__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'您不能自己创建这些附加描述符。即使可以,也不能在为该类生成的实例上为额外的插槽引用分配更多的内存空间,因为这些信息存储在该类的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__
TruePython开发人员在这里所做的就是扩展系统,使用任意名称添加更多这样的插槽,这些名称取自正在创建的类的__slots__属性,这样您就可以节省内存;字典比简单地引用插槽中的值占用更多的内存。通过指定__slots__,您可以禁用__dict__和__weakref__插槽,除非您显式地将它们包含在__slots__序列中。
扩展插槽的唯一方法是子类;您可以使用type()函数或使用工厂函数动态创建一个子类:
def extra_slots_subclass(base, *slots):
class ExtraSlots(base):
__slots__ = slots
ExtraSlots.__name__ = base.__name__
return ExtraSlots发布于 2015-01-13 01:51:43
在我看来是a type turns __slots__ into a tuple as one of it's first orders of action。然后是stores the tuple on the extended type object。因为在这一切之下,python正在寻找一个tuple,所以没有办法改变它。实际上,我甚至不确定您是否可以访问它,除非您首先将一个元组传递给该实例。
您设置的原始对象仍然是类型上的一个属性,这一事实(可能)只是为了便于自省。
您不能修改__slots__并期望它在某个地方显示出来(从可读性的角度来看,您可能并不是真的想这样做,对吧?)
当然,您始终可以通过子类化来扩展插槽:
>>> class C(A):
... __slots__ = ['z']
...
>>> c = C()
>>> c.x = 1
>>> c.z = 1发布于 2015-01-13 02:02:51
您不能在创建类后修改__slots__属性。这是因为它会导致奇怪的行为。
想象一下以下情况。
class A:
__slots__ = ["x"]
a = A()
A.__slots__.append("y")
a.y = None 在这种情况下应该发生什么?最初没有为第二个插槽分配空间,但根据插槽属性,a应该能够为y分配空间。
__slots__不是关于保护哪些名称可以访问,哪些名称不能访问。相反,__slots__是为了减少对象的内存占用。通过尝试修改__slots__,您将无法实现__slots__要实现的优化。
__slots__如何减少内存占用
通常,对象的属性存储在dict中,这本身就需要相当多的内存。如果您正在创建数百万个对象,那么这些dicts所需的空间就会变得令人望而却步。__slots__通知制作类对象的python机制,这个类的实例只会引用这么多的属性,属性的名称是什么。因此,该类可以通过直接将属性存储在实例上而不是存储在dict中来进行优化。它将属性的内存(指向)直接放在对象上,而不是为对象创建新的dict。
https://stackoverflow.com/questions/27907373
复制相似问题