gpt4 book ai didi

python - 为什么 Python 3 中实例的 __dict__ 大小如此之小?

转载 作者:太空狗 更新时间:2023-10-29 16:53:27 25 4
gpt4 key购买 nike

在 Python 中,为类的实例创建的字典与包含该类的相同属性的字典相比很小:

import sys

class Foo(object):
def __init__(self, a, b):
self.a = a
self.b = b

f = Foo(20, 30)

使用 Python 3.5.2 时,以下对 getsizeof 的调用产生:

>>> sys.getsizeof(vars(f))  # vars gets obj.__dict__
96
>>> sys.getsizeof(dict(vars(f))
288

288 - 96 = 192 字节保存!

另一方面,使用 Python 2.7.12 时,相同的调用返回:

>>> sys.getsizeof(vars(f))
280
>>> sys.getsizeof(dict(vars(f)))
280

0 字节保存。

在这两种情况下,字典显然完全相同的内容:

>>> vars(f) == dict(vars(f))
True

所以这不是一个因素。此外,这也仅适用于 Python 3。

那么,这里发生了什么?为什么 Python 3 中实例的 __dict__ 的大小如此之小?

最佳答案

简而言之:

实例 __dict__的实现方式不同于使用 dict 创建的“普通”词典或 {} .实例的字典共享键和散列,并为不同的部分保留一个单独的数组:值。 sys.getsizeof仅在计算实例字典的大小时计算这些值。

多一点:

从 Python 3.3 开始,CPython 中的字典以两种形式之一实现:

实例字典总是以拆分表形式( key 共享字典)实现,它允许给定类的实例共享其 __dict__ 的键(和散列)。并且仅在相应的值上有所不同。

这在PEP 412 -- Key-Sharing Dictionary 中都有描述。 .拆分字典的实现登陆 Python 3.3所以,以前版本的 3家庭以及 Python 2.x没有这个实现。

The implementation of __sizeof__ for dictionaries 考虑了这一事实,并且在计算拆分字典的大小时仅考虑与值数组对应的大小。

谢天谢地,这是不言自明的:

Py_ssize_t size, res;

size = DK_SIZE(mp->ma_keys);
res = _PyObject_SIZE(Py_TYPE(mp));
if (mp->ma_values) /*Add the values to the result*/
res += size * sizeof(PyObject*);
/* If the dictionary is split, the keys portion is accounted-for
in the type object. */
if (mp->ma_keys->dk_refcnt == 1) /* Add keys/hashes size to res */
res += sizeof(PyDictKeysObject) + (size-1) * sizeof(PyDictKeyEntry);
return res;

据我所知,拆分表字典仅为实例的命名空间创建,使用dict(){} (也如 PEP 中所述)总是导致组合字典没有这些好处。


顺便说一句,因为它很有趣,我们总是可以打破这种优化。目前我发现了两种当前方式,一种愚蠢的方式或一种更明智的方案:

  1. 犯傻:

    >>> f = Foo(20, 30)
    >>> getsizeof(vars(f))
    96
    >>> vars(f).update({1:1}) # add a non-string key
    >>> getsizeof(vars(f))
    288

    拆分表只支持字符串键,添加一个非字符串键(这确实使有意义)打破了这个规则,CPython 将拆分表变成一个组合表,失去所有内存 yield 。

  2. 可能发生的场景:

    >>> f1, f2 = Foo(20, 30), Foo(30, 40)
    >>> for i, j in enumerate([f1, f2]):
    ... setattr(j, 'i'+str(i), i)
    ... print(getsizeof(vars(j)))
    96
    288

    在一个类的实例中插入不同的键最终会导致拆分表合并。这不仅仅适用于已经创建的实例;从该类创建的所有 consequent 实例都将具有组合字典而不是拆分字典。

    # after running previous snippet
    >>> getsizeof(vars(Foo(100, 200)))
    288

当然,除了好玩之外,没有充分的理由故意这样做。


如果有人想知道,Python 3.6 的字典实现并没有改变这个事实。上述两种形式的字典虽然仍然可用,但只是进一步压缩(dict.__sizeof__ 的实现也发生了变化,因此从 getsizeof 返回的值应该会出现一些差异。)

关于python - 为什么 Python 3 中实例的 __dict__ 大小如此之小?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42419011/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com