np.frombuffer()
numpy.frombuffer
numpy.frombuffer(buffer, dtype=float, count=-1, offset=0)
Interpret a buffer as a 1-dimensional array.
Parameters:buffer : buffer_like
An object that exposes the buffer interface.
dtype : data-type, optional
Data-type of the returned array; default: float.
count : int, optional
Number of items to read. -1 means all data in the buffer.
offset : int, optional
Start reading the buffer from this offset (in bytes); default: 0.
Notes
If the buffer has data that is not in machine byte-order, this should be specified as part of the data-type, e.g.:
>>>
>>> dt = np.dtype(int)
>>> dt = dt.newbyteorder('>')
>>> np.frombuffer(buf, dtype=dt)
The data of the resulting array will not be byteswapped, but will be interpreted correctly.
Examples
>>>
>>> s = 'hello world'
>>> np.frombuffer(s, dtype='S1', count=5, offset=6)
array(['w', 'o', 'r', 'l', 'd'],
dtype='|S1')
>>>
>>> np.frombuffer(b'\x01\x02', dtype=np.uint8)
array([1, 2], dtype=uint8)
>>> np.frombuffer(b'\x01\x02\x03\x04\x05', dtype=np.uint8, count=3)
array([1, 2, 3], dtype=uint8)
NumPy的ndarray数组对象不能像list⼀样动态地改变其⼤⼩,在做数据采集时很不⽅便。本⽂介绍如何通过np.frombuffer()实现动态数组。
列表对象的内存动态分配
Python的列表对象实际上是⼀个动态指针数组。当列表中没有空间储存新的元素时,列表会动态地改变其⼤⼩,以容纳新的元素。每次改变⼤⼩时,它都会预留⼀部分空间,以降低改变⼤⼩的频率。下⾯的程序可以观察列表的这⼀⾏为。
import sys
import pylab as pl
size = []
for i in xrange(10000):
size.sizeof(size))
pl.plot(size, lw="2")
pl.show()
程序的输出如下图所⽰,图中每个阶梯跳变的位置都表⽰⼀次内存分配,⽽每个阶梯的⾼度表⽰额外分配的内存的⼤⼩。
因此由于往列表中添加新元素时,基本上时间复杂度都为O(1),只有在重新分配内存时,时间复杂度才变为O(n)。由于每次额外分配的内存和列表的长度成正⽐,因此随着列表的增⼤,重新分配内存的次数会减少,从⽽整体上append()⽅法的平均时间复杂度为O(1)。这种动态数组很适合⽤来做数据采集,然⽽由于列表中的每个元素都是对象,⽐较浪费内存,因此⽤列表做⼤量数据的采集并不划算。我们希望通过类似NumPy数组的对象采集数据。
NumPy数组的动态分配
NumPy的数组没有这种动态改变⼤⼩的功能,numpy.append()函数每次都会重新分配整个数组,并把原来的数组复制到新数组中。下⾯的程序模拟列表的动态分配,从⽽实现动态数组:
import numpy as np
class DynamicArray(object):
def __init__(self, item_type):
numpy库是标准库吗self._data = np.zeros(10, dtype=item_type)
self._size = 0
def get_data(self):
return self._data[:self._size]
def append(self, value):
if len(self._data) == self._size:
self._data = np.resize(self._data, int(len(self._data)*1.25))
self._data[self._size] = value
self._size += 1
item_type = np.dtype({
"names":["id", "x", "y", "z"],
"formats":["i4", "f8", "f8", "f8"]})
da = DynamicArray(item_type)
for i in xrange(100):
da.append((i, i*0.1, i*0.2, i*0.3))
data = da.get_data()
⽤array数组采集数据
Python标准库中的array数组也提供了动态分配内存的功能,⽽且它和NumPy数组⼀样直接将数值的⼆进制数据保存在⼀块内存中,因此我们可以先⽤array数组收集数组,然后通过np.frombuffer()将array数组的数据内存直接转换为⼀个NumPy数组。下⾯是⼀个例⼦:
>>> import numpy as np
>>> from array import array
>>> a = array("d", [1,2,3,4])  # 创建⼀个array数组
>>> a
array('d', [1.0, 2.0, 3.0, 4.0])
>>> na = np.frombuffer(a, dtype=np.float) # 通过np.frombuffer()创建⼀个和a共享内存的NumPy数组
>>> na
array([ 1.,  2.,  3.,  4.])
>>> na[1] = 20  # 修改NumPy数组中的第⼀个元素
>>> a
array('d', [1.0, 20.0, 3.0, 4.0])  # array数组中的第⼀个元素也同时改变
array数组只⽀持⼀维,如果我们需要采集多个频道的数据,可以将这些数据依次添加进array数组,然后通过reshape()⽅法将np.frombuffer()所创建的NumPy数组改为⼆维数组。下⾯是⼀个例⼦:
buf = array("d")
for i in range(100):
buf.append(math.sin(i*0.1)) ❶
buf.s(i*0.1))
data = np.frombuffer(buf, dtype=np.float).reshape(-1, 2) ❷
print data
在这个例⼦中,❶我们通过array数组buf采集两个频道的数据,数据采集完毕之后,我们通过np.frombuffer()将其转换为NumPy数组,并通过reshape()将其形状改为(100,2)。
⽤bytearray采集数据
当每个频道的数据类型不同时,就不能采⽤上节所介绍的⽅法了。这时我们可以使⽤bytearray收集数据。bytearray是字节数组,因此我们⾸先需要通过struct模块将Python的数值转换成其字节表⽰形式。如果数据来⾃⼆进制⽂件或者硬件,那么我们得到得已经是字节数据,这个步骤可以省略。下⾯是使⽤bytearray进⾏数据采集的例⼦:
buf = bytearray()
for i in range(100):
dtype = np.dtype({"names":["id","sin","cos"], "formats":["h", "d", "d"]}) ❷
data = np.frombuffer(buf, dtype=dtype) ❸
print data
❶采集三个频道的数据,其中频道1是短整型整数,其类型符号为”h”,频道2和3为双精度浮点数,其类型符号为”d”。类型格式字符串中的”=”表⽰输出得字节数据不进⾏内存对齐。即⼀条数据的字节数为2+8+8=16,如果没有”=”,那么⼀条数据的字节数则为8+8+8=24。
❷定义⼀个dtype对象表⽰⼀条数据的结构,dtype对象缺省不进⾏内存对齐,如果采集数据⽤的bytearray中的数据是内存对齐的话,只需要设置dtype()的align参数为True即可。
❸最后通过np.frombuffer()将bytearray转换为NumPy的结构数组。然后我们就可以通过data[“id”]、data[“sin”]和data[“cos”]访问三个频道的数据了。
np.frombuffer()还可以从字符串创建数组,数组也和字符串共享数据内存,但由于字符串是不可变对象,因此所创建的数组是只读的。如果不需要修改数据,这种⽅法⽐np.fromstring()更快、更节省内存。