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.array和numpy.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就像在这种情况下的普通数据一样.
当您从中创建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.array和np.ndarray支持双方的缓冲协议和迭代,但不知怎么创建的ByteArray从array.array通过迭代获取数据,同时创造一个字节组从numpy.ndarray通过缓冲器协议获取数据.对于这两种类型的C内部,这个切换优先级可能有一些神秘的解释,但我不知道它是什么.
无论如何,说你所看到的a1是"应该"发生的事情并不正确; 如上所示,数据'\x01\x03\x02\x05\x04'实际上并不是array.array通过缓冲协议公开的数据.如果有的话,numpy数组的行为就是你应该从缓冲区协议中得到的; 这是array.array与缓冲协议不一致的行为.
我在两种情况下都得到相同的字节数组:
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+ 中发生了更改,这并不奇怪。