【问题标题】:Disk space - Python dictionary vs list磁盘空间 - Python 字典 vs 列表
【发布时间】:2018-11-19 14:14:38
【问题描述】:

我被要求创建一个倒排索引并以多种方式保存它的二进制文件(压缩和不压缩)。

长话短说,我注意到使用dict 表示比转换为list 占用的磁盘空间少得多。

示例:

dic = {
    'w1': [1,2,3,4,5,6],
    'w2': [2,3,4,5,6],
    'w3': [3,4,5,6],
    'w4': [4,5,6]
}

dic_list = list(dic.items())

import pickle

with open('dic.pickle', 'wb') as handle:
    pickle.dump(dic, handle, protocol=pickle.HIGHEST_PROTOCOL)

with open('dic_list.pickle', 'wb') as handle:
    pickle.dump(dic_list, handle, protocol=pickle.HIGHEST_PROTOCOL)

如果您检查两个文件的大小,您会注意到差异。

所以,我愿意知道它们有何不同以及为何不同。任何额外的信息将不胜感激

【问题讨论】:

标签: python python-3.x list dictionary pickle


【解决方案1】:

dic_list 列表由更多对象组成。您有一个外部元组列表,每个元组都有一个键值对。每个值都是另一个列表。这些元组是您需要更多空间的原因。

字典pickle格式不必使用元组对象来存储键值对;前面已经知道字典由一系列对组成,因此您可以直接序列化每个这样对的键和值,而无需包装元组对象的开销。

您可以使用pickletools module 分析泡菜数据;使用只有一个键值的更简单的字典,您已经可以看到区别:

>>> import pickle, pickletools
>>> pickletools.dis(pickle.dumps({'foo': 42}, protocol=pickle.HIGHEST_PROTOCOL))
    0: \x80 PROTO      4
    2: \x95 FRAME      12
   11: }    EMPTY_DICT
   12: \x94 MEMOIZE    (as 0)
   13: \x8c SHORT_BINUNICODE 'foo'
   18: \x94 MEMOIZE    (as 1)
   19: K    BININT1    42
   21: s    SETITEM
   22: .    STOP
highest protocol among opcodes = 4
>>> pickletools.dis(pickle.dumps(list({'foo': 42}.items()), protocol=pickle.HIGHEST_PROTOCOL))
    0: \x80 PROTO      4
    2: \x95 FRAME      14
   11: ]    EMPTY_LIST
   12: \x94 MEMOIZE    (as 0)
   13: \x8c SHORT_BINUNICODE 'foo'
   18: \x94 MEMOIZE    (as 1)
   19: K    BININT1    42
   21: \x86 TUPLE2
   22: \x94 MEMOIZE    (as 2)
   23: a    APPEND
   24: .    STOP

如果您认为EMPTY_DICT + SETITEM 等同于EMPTY_LIST + APPEND,那么该流中唯一真正的区别在于添加了TUPLE2 / MEMOIZE 操作码对。正是那些操作码占用了额外的空间。

【讨论】:

    【解决方案2】:

    dict 可以原生处理键值对,而list 必须使用单独的容器。

    您的dictDict[K, V] 的直接表示 - 对加上一些结构。由于该结构仅适用于运行时,因此可以忽略存储。

    {'a': 1, 'b': 2}
    

    您的 list 使用了对的助手,导致 List[Tuple[K,V]] - 对加上包装器。由于需要包装器来重建对,因此存储时不能忽略它。

    [('a', 1), ('b', 2)]
    

    您也可以在 pickle 转储中检查它。 list 转储包含附加元组的标记。

    pickle.dumps({'a': 1, 'b': 2}, protocol=0)
    (dp0  # <new dict>
      Va  # string a
     p1
      I1  # integer 1
     sVb  # <setitem key/value>, string b
     p2
      I2  # integer 2
     s.   # <setitem key/value>
    
    pickle.dumps(list({'a': 1, 'b': 2}.items()), protocol=0)
    (lp0    # <new list>
      (Va   # <marker>, string a
      p1
       I1   # integer 1
      tp2   # <make tuple>
     a(Vb   # <append>, <marker>, string b
      p3
       I2   # integer 2
      tp4   # <make tuple>
     a.     # <append>
    

    虽然周围的dictlist 都存储为一系列对,但这些对的存储方式不同。对于dict,只有key、value和stop是平铺的。对于list,每对都需要一个额外的tuple

    【讨论】:

    • protocol 0 与当前协议修订版 4 有很多不同之处,我建议 反对 使用它来说明可能发生的情况。无论如何,pickletools.dis() 的可读性要高得多。
    • @MartijnPieters 好吧,无论哪种方式都存在嵌套,并且 ASCII 对某些人来说不那么令人生畏。关键是有一个额外的容器,而不是它们是如何编码的。那个,再加上你的答案已经显示了更好的dis 输出。 ;)
    • 嵌套存在,不在pickle.dumps()字符串中,也没有操作码的解释。您必须自己添加这些,或者您使用的 IDE 检测到您正在倾倒泡菜。
    • @MartijnPieters 我的意思是元组嵌套在列表中。相关部分是元组也必须被转储。我认为格式的来源并不重要,即使对于大多数人来说,pickle 转储也是低级的乱码。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-07-12
    • 2014-01-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多