Python中为什么要在__slots__中添加__dict__属性?

读 Werkzeug 源码时看到:

class LocalProxy(object):
	__slots__ = ('__local', '__dict__', '__name__', '__wrapped__')
# ...

这样写的目的是什么

sf 上相关的提问没有结论: https://stackoverflow.com/questions/7585284/python-whats-the-point-of-adding-dict-to-slots


Python中为什么要在__slots__中添加__dict__属性?

13 回复

为了支持动态创建属性。


emmm,表示跟楼主有一样的疑惑。按道理来说,使用 __slots__ 就是为了避免创建 __dict__ 这个字典,然而又把 __dict__ 加到 __slots__ 中,实在是有点讲不通。

添加__dict__之后使对象有了动态添加属性的能力,但是定义在__solt

定义在 slots 里面的属性还是不保存在 dict 里面。




你们搞懂我在问什么了吗。。。看#3

我查看了文档,提到__slots__不仅会去掉实例的__dict__属性,还会去掉__weakref__属性。

> This class variable can be assigned a string, iterable, or sequence of strings with variable names used by instances. slots reserves space for the declared variables and prevents the automatic creation of dict and weakref for each instance.

所以这里的目的可能是为了使LocalProxy类不可被弱引用(?存疑)

TLDR:
使用__slots__是为了节约内存使用,但是带来的两个副作用:
1. 没了__dict__,无法动态加属性。
2. 没了__weakref__,无法使用弱引用。
为了克服这两个副作用需要把它们重新加回去。

=================================================
1. 普通的 class 会在 instance 初始化的时候把 attribute 放到__dict__里,也就是说内部维护了一个多余的 dict。
>>> class A():
… def init(self):
… self.x = 1
… self.y = 2

>>> a = A()
>>> a.dict
{‘x’: 1, ‘y’: 2}

2. 为了避免在__dict__里浪费内存,有了__slots__。
>>> class B():
slots = (‘x’, ‘y’)
… def init(self):
… self.x = 1
… self.y = 2

>>> b = B()
>>> b.dict
Traceback (most recent call last):
File “<stdin>”, line 1, in <module>
AttributeError: ‘B’ object has no attribute 'dict
可以看出__dict__消失了。

3. dict__的存在目的是为了能在 instance 里动态加入新的属性,新的属性会加到__dict__里。
>>> a.z = 3
>>> a.dict
{‘x’: 1, ‘y’: 2, ‘z’: 3}

但用了__slots__后就无法动态加属性了。
>>> b.z = 3
Traceback (most recent call last):
File “<stdin>”, line 1, in <module>
AttributeError: ‘B’ object has no attribute 'z’

4. 为了依然能动态加属性,我们在__slots__里重新加入__dict

>>> class C():
slots = (‘x’, ‘y’, ‘dict’)
… def init(self):
… self.x = 1
… self.y = 2

>>> c = C()
>>> c.dict
{}
我们注意到,初始化时__dict__初始是空的,依然比 a 要节约内存。
这时候动态加属性也没问题了。
>>> c.z = 3
>>> c.dict
{‘z’: 3}

5. 具体验证下__slots__到底做了什么。
>>> set(dir(b)) - set(dir(a))
{‘slots’}
>>> set(dir(a)) - set(dir(b))
{‘dict’, ‘weakref’}
可以看出 b 加了__slots__后,相较 a 少了__dict__以及__weakref__。
同理,为了使用弱引用,需要把__weakref__加回去。

但有一点我还存有疑问:当初设计__slots__时为什么要去掉__weakref__?

当然这里有个不严谨的地方:例子里{‘x’: 1, ‘y’: 2}并不一定会比空的 dict 占用更多内存,这和初始时 attribute 的个数,dict 底层实现的初始大小 /load factor 等有关。

class LocalProxy(object):slots = (’__local’, ‘dict’, ‘name’, ‘wrapped’)
In [3] used 0.0312 MiB RAM in 0.10s, peaked 0.00 MiB above current, total RAM usage 33.28 MiB

In [4]: lp = LocalProxy()
In [4] used 0.0391 MiB RAM in 0.10s, peaked 0.00 MiB above current, total RAM usage 33.32 MiB

In [5]: lp
Out[5]: <main.LocalProxy at 0x10324e5f0>
In [5] used 0.0117 MiB RAM in 0.10s, peaked 0.00 MiB above current, total RAM usage 33.33 MiB

In [6]: class LocalProxy(object):
…: pass
…:
In [6] used 0.4688 MiB RAM in 0.11s, peaked 0.00 MiB above current, total RAM usage 33.80 MiB

In [7]: lp = LocalProxy()
In [7] used 0.0508 MiB RAM in 0.10s, peaked 0.00 MiB above current, total RAM usage 33.85 MiB

In [8]: lp
Out[8]: <main.LocalProxy at 0x103349110>
In [8] used 0.0039 MiB RAM in 0.11s, peaked 0.00 MiB above current, total RAM usage 33.86 MiB

--------------------------------------- 看看楼上老哥的例子 -------------------------------------------------

In [9]: class C(object):
…: slots = (‘x’, ‘y’, ‘dict’)
…: def init(self):
…: self.x = 1
…: self.y = 2
…:
In [9] used 0.2305 MiB RAM in 0.10s, peaked 0.00 MiB above current, total RAM usage 34.09 MiB

In [10]: c = C()
In [10] used 0.0000 MiB RAM in 0.10s, peaked 0.00 MiB above current, total RAM usage 34.09 MiB

In [11]: c
Out[11]: <main.C at 0x103147c68>
In [11] used 0.0039 MiB RAM in 0.11s, peaked 0.00 MiB above current, total RAM usage 34.09 MiB

In [12]: class C(object):
…: def init(self):
…: self.x = 1
…: self.y = 2
…:
In [12] used 0.0430 MiB RAM in 0.11s, peaked 0.00 MiB above current, total RAM usage 34.13 MiB

In [13]: c = C()
In [13] used 0.0000 MiB RAM in 0.10s, peaked 0.00 MiB above current, total RAM usage 34.13 MiB

In [14]: c
Out[14]: <main.C at 0x10335d210>
In [14] used 0.0000 MiB RAM in 0.10s, peaked 0.00 MiB above current, total RAM usage 34.13 MiB


事实证明确实是为了尽可能的省内存,想省内存,但是又不想丢弃 dictweakref 这两个功能
只能说细真的细





我明白了,是为了能够转发 被代理 obj 的__dict__属性,我看了别处对LocalProxy的使用没有动态新增实例属性,而LocalProxy的实现里唯一的属性(除开__slots__里的属性)是这个:

python<br><br>def __dict__(self):<br> try:<br> return self._get_current_object().__dict__<br> except RuntimeError:<br> raise AttributeError('__dict__')<br>

LocalProxy 确实有点东西,是再看 flask 的源码吗?


是的,想看看 Flask 的 ctx 怎么实现的

回到顶部