读 Werkzeug 源码时看到:
class LocalProxy(object):
__slots__ = ('__local', '__dict__', '__name__', '__wrapped__')
# ...
这样写的目的是什么
sf 上相关的提问没有结论: https://stackoverflow.com/questions/7585284/python-whats-the-point-of-adding-dict-to-slots
1
yangsi 2019-01-18 17:12:28 +08:00 via iPhone
为了支持动态创建属性。
|
2
brucedone 2019-01-18 17:17:22 +08:00
|
3
j0hnj 2019-01-18 17:27:14 +08:00
emmm,表示跟楼主有一样的疑惑。按道理来说,使用 `__slots__` 就是为了避免创建 `__dict__` 这个字典,然而又把 `__dict__` 加到 `__slots__` 中,实在是有点讲不通。
|
4
yangsi 2019-01-18 17:29:28 +08:00 via iPhone
添加__dict__之后使对象有了动态添加属性的能力,但是定义在__solt
|
5
yangsi 2019-01-18 17:30:26 +08:00 via iPhone
定义在 slots 里面的属性还是不保存在 dict 里面。
|
6
whoami9894 OP |
7
whoami9894 OP 我查看了文档,提到`__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`类不可被弱引用(?存疑) |
8
aijam 2019-01-19 10:31:15 +08:00 2
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__? |
9
aijam 2019-01-19 10:42:18 +08:00
当然这里有个不严谨的地方:例子里{'x': 1, 'y': 2}并不一定会比空的 dict 占用更多内存,这和初始时 attribute 的个数,dict 底层实现的初始大小 /load factor 等有关。
|
10
zh826256645 2019-01-19 10:55:19 +08:00
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 事实证明确实是为了尽可能的省内存,想省内存,但是又不想丢弃 __dict__,__weakref__ 这两个功能 只能说细真的细 |
11
whoami9894 OP @aijam
@j0hnj @zh826256645 我明白了,是为了能够转发 被代理 obj 的__dict__属性,我看了别处对`LocalProxy`的使用没有动态新增实例属性,而`LocalProxy`的实现里唯一的属性(除开`__slots__`里的属性)是这个: ```python @property def __dict__(self): try: return self._get_current_object().__dict__ except RuntimeError: raise AttributeError('__dict__') ``` |
12
zh826256645 2019-01-19 16:35:33 +08:00
LocalProxy 确实有点东西,是再看 flask 的源码吗?
|
13
whoami9894 OP @zh826256645
是的,想看看 Flask 的 ctx 怎么实现的 |