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

ely*_*ely 3 python buffer numpy 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)
Run Code Online (Sandbox Code Playgroud)

由于两个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')
Run Code Online (Sandbox Code Playgroud)

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

但即使直接查看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')
Run Code Online (Sandbox Code Playgroud)

同样的失败发生在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')
Run Code Online (Sandbox Code Playgroud)

我注意到,如果我给出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
Run Code Online (Sandbox Code Playgroud)

AFAICT np.int32应该与array 'L'typecode 相对应,但是对于为什么不能解释的任何解释都会非常有用.

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

Bre*_*arn 6

当您从中创建bytearray时array.array,它将其视为可迭代的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')
Run Code Online (Sandbox Code Playgroud)

也就是说,直接从数组创建一个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)
Run Code Online (Sandbox Code Playgroud)

该行为一直困扰着,虽然,因为这两个array.arraynp.ndarray支持双方的缓冲协议迭代,但不知怎么创建的ByteArray从array.array通过迭代获取数据,同时创造一个字节组从numpy.ndarray通过缓冲器协议获取数据.对于这两种类型的C内部,这个切换优先级可能有一些神秘的解释,但我不知道它是什么.

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


hpa*_*ulj 5

我在两种情况下都得到相同的字节数组:

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')
Run Code Online (Sandbox Code Playgroud)

在这两种情况下,我都有 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'
Run Code Online (Sandbox Code Playgroud)

我可以通过更改 dtype 来删除额外的字节:

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

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+ 中发生了更改,这并不奇怪。