首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何动态更改__slots__属性?

如何动态更改__slots__属性?
EN

Stack Overflow用户
提问于 2015-01-13 01:18:40
回答 4查看 4.5K关注 0票数 9

假设我有一个包含__slots__的类

代码语言:javascript
复制
class A:
    __slots__ = ['x']

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

现在我要更改A__slots__

代码语言:javascript
复制
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__,让新的实例拥有修改后的属性?

EN

回答 4

Stack Overflow用户

发布于 2015-01-13 01:52:22

您不能在创建类后动态更改__slots__属性。这是因为该值用于为每个插槽创建特殊的。从__slots__ documentation

通过为每个变量名创建描述符(实现描述符),可以在类级别实现

__slots__。因此,不能使用类属性为__slots__定义的实例变量设置默认值;否则,类属性将覆盖描述符分配。

您可以在类__dict__中看到描述符

代码语言:javascript
复制
>>> 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__属性总是作为插槽实现的:

代码语言:javascript
复制
>>> 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()函数或使用工厂函数动态创建一个子类:

代码语言:javascript
复制
def extra_slots_subclass(base, *slots):
    class ExtraSlots(base):
        __slots__ = slots
    ExtraSlots.__name__ = base.__name__
    return ExtraSlots
票数 19
EN

Stack Overflow用户

发布于 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__并期望它在某个地方显示出来(从可读性的角度来看,您可能并不是真的想这样做,对吧?)

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

代码语言:javascript
复制
>>> class C(A):
...   __slots__ = ['z']
... 
>>> c = C()
>>> c.x = 1
>>> c.z = 1
票数 3
EN

Stack Overflow用户

发布于 2015-01-13 02:02:51

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

想象一下以下情况。

代码语言:javascript
复制
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

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/27907373

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档