在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产生:
getsizeof
>>> sys.getsizeof(vars(f)) # vars gets obj.__dict__ 96 >>> sys.getsizeof(dict(vars(f)) 288
288 - 96 = 192 节省了字节!
288 - 96 = 192
另一方面,使用Python 2.7.12,则返回相同的调用:
>>> sys.getsizeof(vars(f)) 280 >>> sys.getsizeof(dict(vars(f))) 280
0 已保存字节。
0
在这两种情况下,字典显然具有 完全相同的内容 :
>>> vars(f) == dict(vars(f)) True
所以这不是一个因素。此外,这也仅适用于Python 3。
那么,这是怎么回事?为什么__dict__在Python 3中实例的大小如此之小?
__dict__
实例__dict__的实现方式与用dict或创建的“普通”词典的实现方式不同{}。实例的字典 共享 键和哈希,并为不同的部分(值)保留一个单独的数组。sys.getsizeof仅在计算实例字典的大小时才计算这些值。
dict
{}
sys.getsizeof
从Python 3.3开始,CPython中的字典以以下两种形式之一实现:
me_value
PyDictKeyEntry
ma_values
PyDictObject
实例字典 始终 以拆分表形式(“密钥共享字典”)实现,该字典表允许给定类的实例为其共享密钥(和散列),__dict__并且仅在相应值上有所不同。
所有这些都在PEP 412-密钥共享字典中进行了描述。拆分字典的实现位于Python中,3.3因此该3系列的早期版本以及Python2.x都没有此实现。
3.3
3
2.x
__sizeof__for字典的实现考虑到了这一事实,并且在计算拆分字典的大小时仅考虑与values数组对应的大小。
__sizeof__
值得庆幸的是,自我解释:
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中所述) 始终会 导致组合字典没有这些好处。
dict()
顺便说一句,因为它很有趣,所以我们总是可以打破这种优化。我目前发现了两种当前的方法,一种愚蠢的方法,或者是一种更为明智的情况:
>>> f = Foo(20, 30)
getsizeof(vars(f)) 96 vars(f).update({1:1}) # add a non-string key getsizeof(vars(f)) 288
拆分表仅支持字符串键,添加非字符串键(这实际上是 零 意义)会破坏该规则,CPython将拆分表变成一个合并的表,从而失去了所有内存。
>>> 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
在类的实例中插入不同的键最终将导致拆分表被合并。这并不仅仅适用于已经创建的实例。从该类创建的所有 后续 实例将具有组合字典,而不是拆分字典。
# after running previous snippet >>> getsizeof(vars(Foo(100, 200))) 288
当然,除了娱乐之外,没有其他理由是故意这样做的。
如果有人想知道,Python 3.6的字典实现不会改变这一事实。前面提到的两种形式的字典仍然可以使用(它们的实现dict.__sizeof__也作了更改,因此从中返回的值应该有所区别)被进一步压缩getsizeof。
dict.__sizeof__