为什么要在__slots__中添加__dict__属性? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
whoami9894
V2EX    Python

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

  •  
  •   whoami9894 2019-01-18 15:44:46 +08:00 3115 次点击
    这是一个创建于 2512 天前的主题,其中的信息可能已经有所发展或是发生改变。

    读 Werkzeug 源码时看到:

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

    这样写的目的是什么

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

    13 条回复    2019-01-19 16:50:01 +08:00
    yangsi
        1
    yangsi  
       2019-01-18 17:12:28 +08:00 via iPhone
    为了支持动态创建属性。
    brucedone
        2
    brucedone  
       2019-01-18 17:17:22 +08:00
    j0hnj
        3
    j0hnj  
       2019-01-18 17:27:14 +08:00
    emmm,表示跟楼主有一样的疑惑。按道理来说,使用 `__slots__` 就是为了避免创建 `__dict__` 这个字典,然而又把 `__dict__` 加到 `__slots__` 中,实在是有点讲不通。
    yangsi
        4
    yangsi  
       2019-01-18 17:29:28 +08:00 via iPhone
    添加__dict__之后使对象有了动态添加属性的能力,但是定义在__solt
    yangsi
        5
    yangsi  
       2019-01-18 17:30:26 +08:00 via iPhone
    定义在 slots 里面的属性还是不保存在 dict 里面。
    whoami9894
        6
    whoami9894  
    OP
       2019-01-18 23:28:08 +08:00 via Android
    @yangsi
    @brucedone

    你们搞懂我在问什么了吗。。。看#3
    whoami9894
        7
    whoami9894  
    OP
       2019-01-19 00:03:04 +08:00
    我查看了文档,提到`__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`类不可被弱引用(?存疑)
    aijam
        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__?
    aijam
        9
    aijam  
       2019-01-19 10:42:18 +08:00
    当然这里有个不严谨的地方:例子里{'x': 1, 'y': 2}并不一定会比空的 dict 占用更多内存,这和初始时 attribute 的个数,dict 底层实现的初始大小 /load factor 等有关。
    zh826256645
        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__ 这两个功能
    只能说细真的细
    whoami9894
        11
    whoami9894  
    OP
       2019-01-19 15:38:58 +08:00
    @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__')
    ```
    zh826256645
        12
    zh826256645  
       2019-01-19 16:35:33 +08:00
    LocalProxy 确实有点东西,是再看 flask 的源码吗?
    whoami9894
        13
    whoami9894  
    OP
       2019-01-19 16:50:01 +08:00
    @zh826256645
    是的,想看看 Flask 的 ctx 怎么实现的
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     5243 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 25ms UTC 08:43 PVG 16:43 LAX 00:43 JFK 03:43
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86