如何动态更改_插槽_属性?

2024-09-27 04:29:10 发布

您现在位置:Python中文网/ 问答频道 /正文

假设我有一个__slots__的类

class A:
    __slots__ = ['x']

a = A()
a.x = 1   # works fine
a.y = 1   # AttributeError (as expected)

现在我要改变{}的{}

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__,以便新实例具有修改后的属性


Tags: 实例属性asokclassattributeerrorexpectedprint
3条回答

不能在创建类之后动态更改__slots__属性。这是因为该值用于为每个插槽创建特殊的descriptors。从^{} documentation开始:

__slots__ are implemented at the class level by creating descriptors (Implementing Descriptors) for each variable name. As a result, class attributes cannot be used to set default values for instance variables defined by __slots__; otherwise, the class attribute would overwrite the descriptor assignment.

您可以在类__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__
True

Python开发人员在这里所做的全部工作就是扩展系统,使用任意名称添加更多此类插槽,这些名称取自所创建类的__slots__属性,以便节省内存;字典比插槽中的值的简单引用占用更多内存。通过指定__slots__,可以禁用__dict____weakref__插槽,除非在__slots__序列中显式包含这些插槽

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

def extra_slots_subclass(base, *slots):
    class ExtraSlots(base):
        __slots__ = slots
    ExtraSlots.__name__ = base.__name__
    return ExtraSlots

在我看来a type turns ^{} 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

创建类后不能修改__slots__属性。这是因为它会导致奇怪的行为

想象一下下面的情景

class A:
    __slots__ = ["x"]

a = A()
A.__slots__.append("y")
a.y = None 

在这种情况下会发生什么?最初没有为第二个插槽分配空间,但是根据slots属性,a应该能够为y分配空间

__slots__并不是保护可以访问和不能访问的名称。相反__slots__是关于减少对象的内存占用。通过尝试修改__slots__,您将无法实现__slots__想要实现的优化

插槽如何减少内存占用

通常,对象的属性存储在dict中,这本身就需要相当多的内存。如果要创建数百万个对象,则这些命令所需的空间将变得不可接受__slots__通知生成类对象的python机制,该类的实例引用的属性将非常多,属性的名称将是什么。因此,该类可以通过将属性直接存储在实例上而不是存储在dict中来进行优化。它将(指向的指针)属性的内存直接放在对象上,而不是为对象创建一个新的dict

相关问题 更多 >

    热门问题