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 · 2658 次点击
    这是一个创建于 2143 天前的主题,其中的信息可能已经有所发展或是发生改变。

    读 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   ·   实用小工具   ·   2529 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 29ms · UTC 06:00 · PVG 14:00 · LAX 22:00 · JFK 01:00
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.