Python 如何动态更改_插槽_属性?
假设我有一个带有Python 如何动态更改_插槽_属性?,python,python-3.x,slots,Python,Python 3.x,Slots,假设我有一个带有\uuuuu插槽的类 class A: __slots__ = ['x'] a = A() a.x = 1 # works fine a.y = 1 # AttributeError (as expected) 现在我要更改A的插槽 A.__slots__.append('y') print(A.__slots__) # ['x', 'y'] b = A() b.x = 1 # OK b.y = 1 # AttributeError (why?)
\uuuuu插槽的类
class A:
__slots__ = ['x']
a = A()
a.x = 1 # works fine
a.y = 1 # AttributeError (as expected)
现在我要更改A
的插槽
A.__slots__.append('y')
print(A.__slots__) # ['x', 'y']
b = A()
b.x = 1 # OK
b.y = 1 # AttributeError (why?)
b
是在A
的\uuuuuuuuuuuuuuuuuuu
更改后创建的,因此原则上Python可以为b.y
分配内存。为什么没有
如何正确地修改一个类的\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
,以使新实例具有修改后的属性?我觉得。那好吧。因为在这一切之下,python正在寻找一个元组
,所以没有办法对它进行变异。事实上,我甚至不确定您是否可以访问它,除非您首先向实例传递一个元组
您设置的原始对象仍然作为类型的一个属性保留(也许)只是一种方便的内省
你不能修改\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
当然,您始终可以使用子类来扩展插槽:
>>> class C(A):
... __slots__ = ['z']
...
>>> c = C()
>>> c.x = 1
>>> c.z = 1
在创建类之后,不能动态更改\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu。从:
\uu\u插槽
通过为每个变量名创建描述符(实现描述符)在类级别实现。因此,类属性不能用于设置由定义的实例变量的默认值;否则,class属性将覆盖描述符赋值
您可以在类中看到描述符
>>> 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'
Python开发人员在这里所做的只是扩展系统,使用任意名称添加更多的此类插槽,这些名称取自所创建类的\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu>属性,以便节省内存;字典比插槽中的值的简单引用占用更多内存。通过指定\uuuu插槽
可以禁用\uuuuu指令
和\uuuuuuuukref\uuuuu
插槽,除非明确将这些插槽包括在\uuuuu插槽
序列中
扩展槽的唯一方法是子类;您可以使用type()
函数或使用工厂函数动态创建子类:
def extra_slots_subclass(base, *slots):
class ExtraSlots(base):
__slots__ = slots
ExtraSlots.__name__ = base.__name__
return ExtraSlots
创建类后,您不能修改\uuuuuuuuuuuuuuuuuuuuuuuuuuuuu
属性。这是因为它会导致奇怪的行为
想象一下下面的情景
class A:
__slots__ = ["x"]
a = A()
A.__slots__.append("y")
a.y = None
在这种情况下会发生什么?最初没有为第二个插槽分配空间,但根据插槽属性,a
应该能够为y
分配空间
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
并不是为了保护可以访问和不能访问的名称。相反,\uuuuuuu插槽
是关于减少对象的内存占用。通过尝试修改\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
插槽如何减少内存占用
通常,对象的属性存储在dict
中,这本身就需要相当多的内存。如果要创建数百万个对象,则这些命令所需的空间将变得不可接受\uuuu slots\uuuu
通知生成类对象的python机制,该类的实例引用的属性将非常多,属性的名称将是什么。因此,该类可以通过将属性直接存储在实例上而不是存储在dict
中来进行优化。它将属性(指向属性的指针)的内存直接放在对象上,而不是为对象创建一个新的dict
。将这些问题的答案放在一起,我想强调一下这个问题的解决方案:
您可以通过创建具有相同名称的子类,然后用其子类替换父类来修改\uuuuuuuuuuu slots\uuuuuuu
。请注意,您可以对在任何模块中声明和使用的类执行此操作,而不仅仅是您的类
考虑以下模块,该模块声明了一些类:
>>> import module
>>> from module import A
>>>
>>> # for classes imported into your module:
>>> A = type('A', (A,), {'__slots__': ('foo',)})
>>> # for classes which will be instantiated by the `module` itself:
>>> module.B = type('B', (module.B,), {'__slots__': ('bar',)})
>>>
>>> a = A()
>>> a.x = 1
>>> a.foo = 2
>>>
>>> b = a.b
>>> b.z = 3
>>> b.bar = 4
>>>
模块.py:
class A(object):
# some class a user should import
__slots__ = ('x', 'b')
def __init__(self):
self.b = B()
class B(object):
# let's suppose we can't use it directly,
# it's returned as a part of another class
__slots__ = ('z',)
from module import A
def get_instance():
return A()
以下是如何向这些类添加属性:
>>> import module
>>> from module import A
>>>
>>> # for classes imported into your module:
>>> A = type('A', (A,), {'__slots__': ('foo',)})
>>> # for classes which will be instantiated by the `module` itself:
>>> module.B = type('B', (module.B,), {'__slots__': ('bar',)})
>>>
>>> a = A()
>>> a.x = 1
>>> a.foo = 2
>>>
>>> b = a.b
>>> b.z = 3
>>> b.bar = 4
>>>
但是,如果您使用模块
从某个第三方模块接收类实例,该怎么办
模块第三方。py:
class A(object):
# some class a user should import
__slots__ = ('x', 'b')
def __init__(self):
self.b = B()
class B(object):
# let's suppose we can't use it directly,
# it's returned as a part of another class
__slots__ = ('z',)
from module import A
def get_instance():
return A()
没问题,它也会工作的!唯一的区别是,您可能需要在导入第三方模块之前对它们进行修补(如果它从模块导入类
):
这是因为Python只导入一次模块,然后在所有其他模块之间共享它们,所以您对模块所做的更改会影响您的模块中运行的所有代码。当然,请在这里闲逛,践踏我的答案;-)+1但是对于有描述符的有趣演示。@mgilson::-P您的在这张图片中同样有效,所以我已经投票支持您了。谢谢您的投票:-)。我开始怀疑我是否在杂草丛中,是否应该删除我的答案。。。不管怎样,最近我一直在寻找机会去挖掘C代码,因为,为什么不呢?在玩了几年描述符之后,我开始把一个描述符看作是它自己的dict,例如:def\uu get\uuuu(dsc,obj,cls=None):如果cls不是其他的,则返回dsc。Map[obj]
其中Map
将是描述符实例上的dict,其中包含与obj实例关联的值。。。这让我怀疑描述符是否真的能节省内存。@Tcll你把插槽和描述符搞混了。插槽是描述符的一种特殊形式。带有插槽的对象有点像一个可变的命名元组,其中属性名只是获取给定索引处元素的方便方法。插槽描述符是在C级实现的,基本上只是*(self+att)