从NumPy数组创建Python bytearray时,额外数据来自何处?

时间:2016-03-06 22:27:42

标签: python numpy buffer bytearray protocol-buffers

考虑两种天真地制作相同bytearray的方法(使用Python 2.7.11,但在3.4.3中也确认了相同的行为):

In [80]: from array import array

In [81]: import numpy as np    

In [82]: a1 = array('L',  [1, 3, 2, 5, 4])

In [83]: a2 = np.asarray([1,3,2,5,4], dtype=int)

In [84]: b1 = bytearray(a1)

In [85]: b2 = bytearray(a2)

由于array.arraynumpy.ndarray都支持缓冲区协议,因此我希望在转换为bytearray时导出相同的基础数据。

但是上面的数据:

In [86]: b1
Out[86]: bytearray(b'\x01\x03\x02\x05\x04')

In [87]: b2
Out[87]: bytearray(b'\x01\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00')

起初我以为在NumPy数组上对bytearray的简单调用可能会因数据类型,连续性或其他一些开销数据而无意中获得一些额外的字节。

但即使直接查看NumPy缓冲区数据句柄,它仍然表示大小为40并提供相同的数据:

In [90]: a2.data
Out[90]: <read-write buffer for 0x7fb85d60fee0, size 40, offset 0 at 0x7fb85d668fb0>

In [91]: bytearray(a2.data)
Out[91]: bytearray(b'\x01\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00')

a2.view()发生同样的失败:

In [93]: bytearray(a2.view())
Out[93]: bytearray(b'\x01\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00')

我注意到如果我给dtype=np.int32,那么bytearray(a2)的长度是20而不是40,这表明额外的字节与类型信息有关 - 它只是不清楚为什么或如何:

In [20]: a2 = np.asarray([1,3,2,5,4], dtype=int)

In [21]: len(bytearray(a2.data))
Out[21]: 40

In [22]: a2 = np.asarray([1,3,2,5,4], dtype=np.int32)

In [23]: len(bytearray(a2.data))
Out[23]: 20

AFAICT,np.int32应该与array 'L'类型代码相对应,但是对于为什么不对其进行任何解释都会有很大的帮助。

如何才能可靠地仅提取数据的一部分&#34;应该&#34;通过缓冲协议导出...就像在这种情况下的普通array数据一样。

2 个答案:

答案 0 :(得分:6)

当您从array.array创建bytearray时,它会将其视为一个可迭代的int,而不是缓冲区。你可以看到这个因为:

>>> bytearray(a1)
bytearray(b'\x01\x03\x02\x05\x04')
>>> bytearray(buffer(a1))
bytearray(b'\x01\x00\x00\x00\x03\x00\x00\x00\x02\x00\x00\x00\x05\x00\x00\x00\x04\x00\x00\x00')

也就是说,直接从数组创建一个bytearray会给你“普通”整数,但是从数组的缓冲区创建一个bytearray可以得到这些整数的实际字节表示。此外,您不能从具有不适合单个字节的整数的数组创建一个bytearray:

>>> bytearray(array.array(b'L', [256]))
Traceback (most recent call last):
  File "<pyshell#38>", line 1, in <module>
    bytearray(array.array(b'L', [256]))
ValueError: byte must be in range(0, 256)

但是,行为仍然令人费解,因为array.arraynp.ndarray都支持缓冲协议迭代,但不知何故创建了来自array.array的bytearray通过迭代获取数据,而从numpy.ndarray创建bytearray则通过缓冲协议获取数据。对于这两种类型的C内部,这个切换优先级可能有一些神秘的解释,但我不知道它是什么。

无论如何,说你用a1看到的是“应该”发生的事情并不正确;如上所示,数据'\x01\x03\x02\x05\x04'实际上不是array.array通过缓冲协议公开的内容。如果有的话,numpy数组的行为就是你应该从缓冲区协议中得到的; array.array行为与缓冲协议不一致。

答案 1 :(得分:3)

我在两种情况下得到相同的bytearray:

In [1032]: sys.version
Out[1032]: '3.4.3 (default, Mar 26 2015, 22:07:01) \n[GCC 4.9.2]'
In [1033]: from array import array

In [1034]: a1=array('L',[1,3,2,5,4])
In [1035]: a2=np.array([1,3,2,5,4],dtype=np.int32)

In [1036]: bytearray(a1)
Out[1036]: bytearray(b'\x01\x00\x00\x00\x03\x00\x00\x00\x02\x00\x00\x00\x05\x00\x00\x00\x04\x00\x00\x00')
In [1037]: bytearray(a2)
Out[1037]: bytearray(b'\x01\x00\x00\x00\x03\x00\x00\x00\x02\x00\x00\x00\x05\x00\x00\x00\x04\x00\x00\x00')

在这两种情况下,我有5个数字,每个占用4个字节(32位整数) - 20个字节。

bytearray可能会要求以下方法(或类似的东西):

In [1038]: a1.tobytes()
Out[1038]: b'\x01\x00\x00\x00\x03\x00\x00\x00\x02\x00\x00\x00\x05\x00\x00\x00\x04\x00\x00\x00'
In [1039]: a2.tostring()
Out[1039]: b'\x01\x00\x00\x00\x03\x00\x00\x00\x02\x00\x00\x00\x05\x00\x00\x00\x04\x00\x00\x00'

我可以通过更改dtype来删除多余的字节:

In [1059]: a2.astype('i1').tostring()
Out[1059]: b'\x01\x03\x02\x05\x04'

https://docs.python.org/2.6/c-api/buffer.html

  

从版本1.6开始,Python一直在提供Python级缓冲区对象和C级缓冲区API,以便任何内置或使用定义的类型都可以公开其特性。但是,由于存在各种缺点,两者都已被弃用,并且已在Python 3.0中正式删除,以支持新的C级缓冲区API和名为memoryview的新Python级对象。

     

新的缓冲区API已被反向移植到Python 2.6,并且memoryview对象已被反向移植到Python 2.7。强烈建议使用它们而不是旧的API,除非出于兼容性原因而阻止这样做。

鉴于缓冲区界面中的这些变化,旧的array模块在​​2.6和2.7中未更改,但在3.0 +中已更改,这并不奇怪。