我在Python解释器中执行以下代码:
len(pickle.dumps([numpy.random.random(384).tolist()]*55))
它提供3584。假设numpy.random.random产生8字节浮点数,则被pickle的数据为384 * 8 * 55 = 168,960字节。为什么len给出这么小的数字?
numpy.random.random
len
xpcnnkqh1#
当你乘一个列表的列表时,会添加一个内部列表的浅副本。举例来说:
a = [[7, 8, 9]] * 5 id(a[0]) == id(a[1]) # True
换句话说,a等于:
reference_to_inner_list = [7, 8, 9] a = [ reference_to_inner_list, reference_to_inner_list, reference_to_inner_list, reference_to_inner_list, reference_to_inner_list, ]
pickle按原样序列化这个对象,所以序列化的对象也是一个引用列表。幸运的是,我们有一个人类可读的协议,实际上可视化这一点。
pickle.dumps([[7,8,9]], protocol=0) pickle.dumps([[7,8,9]]*5, protocol=0)
输出:
b'(lp0\n(lp1\nI7\naI8\naI9\naa.' b'(lp0\n(lp1\nI7\naI8\naI9\naag1\nag1\nag1\nag1\na.' ^ ^ ^ ^ ^ ^ ^ 7 8 9 r-2 r-3 r-4 r-5
其中r-2,...、r-5分别是第二至第五个参考。正如您所看到的,与实际列表相比,引用非常小。这也是为什么你的目标如此之小的原因。你的对象由384个浮点数和54个对该列表的引用组成。如果对象是一个平面列表,那么它的大小将会爆炸,因为它不再是一个引用。
r-2
r-5
len(pickle.dumps(numpy.random.random(384).tolist()*55)) # 190156
这种行为对于pickle来说是必不可少的,例如在处理树结构数据时。如果pickle不能将一个引用转换为一个引用,那么树结构将被破坏。
对于有问题的对象,不执行压缩,因为它们是浮点数。例如,如下所示,浮点数组的大小大约是元素数x 9字节(我猜8字节用于数据,1字节用于追加操作)。
len(pickle.dumps(numpy.zeros(384*55).tolist())) # 190156 384*55*9 # 190080
这表明没有对这个对象执行压缩,但这并不意味着pickle根本没有进行压缩。例如,如果值是整数,如下所示,它会更小。
len(pickle.dumps(numpy.zeros(384*55, dtype=numpy.int64).tolist())) # 42298 384*55*2 # 42240
所以,pickle做了某种压缩(或编码),但不是在这种情况下。
yhuiod9q2#
你创建了一个嵌套列表,而一个包含384个0到1之间的随机浮点数的列表有55个副本。这可能不是您所期望的,但您可以在此处看到输出
import numpy as np res = [np.random.random(384).tolist()]*55 print(res)
如果你想做整数列表,你会想用np.random.randint(0,9)代替。至于为什么pickle对象的具体大小,那就稍微复杂一点,并不是直接根据字节大小来计算的。这是pickle数据格式的结果,它不直接将数据存储为字符串,而是使用一组python特定的操作码来分解python对象,并以压缩的方式将它们存储为一系列字节。unpickle进程读取这些操作码以将对象重建回其原始状态。如果你以字节格式查看pickle对象,你不会看到任何与列表中的值相似的内容,因为所有内容都是用它自己的内部语言编码的。你不会得到一个字节计数,因为它是一个压缩格式,是一个答案。如果你想弄清楚对象的确切大小,你必须深入研究文件中使用的每个操作码的存储大小,并从那里建立存储消耗,然后将其存储为字节。参考文献:Pickle操作码Source -编码发生的地方Pickler类对象
2条答案
按热度按时间xpcnnkqh1#
当你乘一个列表的列表时,会添加一个内部列表的浅副本。
举例来说:
换句话说,a等于:
pickle按原样序列化这个对象,所以序列化的对象也是一个引用列表。
幸运的是,我们有一个人类可读的协议,实际上可视化这一点。
输出:
其中
r-2
,...、r-5
分别是第二至第五个参考。正如您所看到的,与实际列表相比,引用非常小。这也是为什么你的目标如此之小的原因。你的对象由384个浮点数和54个对该列表的引用组成。
如果对象是一个平面列表,那么它的大小将会爆炸,因为它不再是一个引用。
这种行为对于pickle来说是必不可少的,例如在处理树结构数据时。如果pickle不能将一个引用转换为一个引用,那么树结构将被破坏。
编辑:
对于有问题的对象,不执行压缩,因为它们是浮点数。例如,如下所示,浮点数组的大小大约是元素数x 9字节(我猜8字节用于数据,1字节用于追加操作)。
这表明没有对这个对象执行压缩,但这并不意味着pickle根本没有进行压缩。例如,如果值是整数,如下所示,它会更小。
所以,pickle做了某种压缩(或编码),但不是在这种情况下。
yhuiod9q2#
你创建了一个嵌套列表,而一个包含384个0到1之间的随机浮点数的列表有55个副本。这可能不是您所期望的,但您可以在此处看到输出
如果你想做整数列表,你会想用np.random.randint(0,9)代替。
至于为什么pickle对象的具体大小,那就稍微复杂一点,并不是直接根据字节大小来计算的。这是pickle数据格式的结果,它不直接将数据存储为字符串,而是使用一组python特定的操作码来分解python对象,并以压缩的方式将它们存储为一系列字节。unpickle进程读取这些操作码以将对象重建回其原始状态。
如果你以字节格式查看pickle对象,你不会看到任何与列表中的值相似的内容,因为所有内容都是用它自己的内部语言编码的。
你不会得到一个字节计数,因为它是一个压缩格式,是一个答案。如果你想弄清楚对象的确切大小,你必须深入研究文件中使用的每个操作码的存储大小,并从那里建立存储消耗,然后将其存储为字节。
参考文献:
Pickle操作码
Source -编码发生的地方
Pickler类对象