从cStringIO对象创建Numpy数组并避免复制
这是为了更好地理解事情。这并不是我需要解决的实际问题。一个 cstringIO
对象应该模拟一个字符串、一个文件,同时也能像迭代器一样遍历每一行。那它是否也模拟了一个缓冲区呢?无论如何,理想情况下,我们应该能够像下面这样构建一个 numpy 数组。
import numpy as np
import cstringIO
c = cStringIO.StringIO('\x01\x00\x00\x00\x01\x00\x00\x00')
#Trying the iterartor abstraction
b = np.fromiter(c,int)
# The above fails with: ValueError: setting an array element with a sequence.
#Trying the file abstraction
b = np.fromfile(c,int)
# The above fails with: IOError: first argument must be an open file
#Trying the sequence abstraction
b = np.array(c, int)
# The above fails with: TypeError: long() argument must be a string or a number
#Trying the string abstraction
b = np.fromstring(c)
#The above fails with: TypeError: argument 1 must be string or read-only buffer
b = np.fromstring(c.getvalue(), int) # does work
我的问题是,为什么它会这样表现。
这个问题的实际情况是这样的:我有一个迭代器,它会返回一个元组。我想从这个元组的某个部分创建一个 numpy 数组,并且尽量减少复制和重复。我最开始的做法是把元组中有用的部分写入一个 StringIO 对象,然后用它的内存缓冲区来创建数组。当然,我可以使用 getvalue()
,但这会创建并返回一个副本。有什么好的方法可以避免额外的复制呢?
2 个回答
因为 cStringIO
不支持缓冲区接口,所以如果它的 getvalue
方法返回的是数据的一个副本,那就没有办法不复制地获取这些数据。
如果 getvalue
直接返回字符串而不复制,那么使用 numpy.frombuffer(x.getvalue(), dtype='S1')
可以得到一个只读的 numpy 数组,这个数组是指向那个字符串的,不需要额外的复制。
为什么 np.fromiter(c, int)
和 np.array(c, int)
不起作用,是因为 cStringIO
在迭代时一次只返回一行,跟文件的行为类似:
>>> list(iter(c))
['\x01\x00\x00\x00\x01\x00\x00\x00']
这样一长串字符串是无法直接转换成一个整数的。
***
除非真的出现问题,否则最好不要太担心复制的问题。原因是,比如使用生成器并传递给 numpy.fromiter
的额外开销,可能比构建一个列表然后传给 numpy.array
的开销还要大——在某些情况下,复制的成本可能比 Python 的运行时开销要低。
不过,如果问题出在内存上,一个解决方案是直接把数据放到最终的 Numpy 数组里。如果你提前知道数组的大小,可以先分配好。如果大小不确定,可以使用 .resize()
方法来根据需要扩展数组。
问题似乎出在numpy不喜欢接收字符而不是数字。记住,在Python中,单个字符和字符串是同一种类型——numpy在内部必须有一些类型检测的机制,它把'\x01'
当作一个嵌套的序列。
另一个问题是cStringIO
是按行迭代的,而不是按字符迭代。
下面这样的迭代器应该能解决这两个问题:
def chariter(filelike):
octet = filelike.read(1)
while octet:
yield ord(octet)
octet = filelike.read(1)
使用时要这样做(注意要调用seek!):
c.seek(0)
b = np.fromiter(chariter(c), int)