将Python中的字节从Numpy数组复制到字符串或bytearray中

时间:2015-02-05 10:40:18

标签: python arrays numpy buffer

我在while循环中从UDP套接字读取数据。

我需要最有效的方式

1)读取数据(*)(已经解决了,但评论很感激)

2)定期将(操纵的)数据转储到文件(**)(问题)

我预计numpy的“tostring”方法存在瓶颈。让我们考虑下面一段(不完整的)代码:

import socket
import numpy

nbuf=4096
buf=numpy.zeros(nbuf,dtype=numpy.uint8) # i.e., an array of bytes
f=open('dump.data','w')

datasocket=socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# ETC.. (code missing here) .. the datasocket is, of course, non-blocking

while True:
  gotsome=True
  try:
    N=datasocket.recv_into(buf) # no memory-allocation here .. (*)
  except(socket.error):
    # do nothing ..
    gotsome=False

  if (gotsome):
    # the bytes in "buf" will be manipulated in various ways ..
    # the following write is done frequently (not necessarily in each pass of the while loop):
    f.write(buf[:N].tostring())  # (**) The question: what is the most efficient way to do this?

f.close() 

现在,在(**),据我所知:

1)buf [:N]为一个新的数组对象分配内存,长度为N + 1,对吧? (也许不是)

..之后:

2)buf [:N] .tostring()为新字符串分配内存,并将buf中的字节复制到该字符串中

这似乎是很多内存分配&交换。在同一个循环中,将来,我将读取几个套接字并写入几个文件。

有没有办法让f.write直接访问“buf”的内存地址,从0到N字节并将它们写入磁盘?

即,在缓冲接口的精神下这样做,并避免这两个额外的内存分配?

P上。 S. f.write(buf [:N] .tostring())等同于buf [:N] .tofile(f)

1 个答案:

答案 0 :(得分:7)

基本上,听起来您想要使用数组的tofile方法或直接使用ndarray.data缓冲区对象。

对于您的确切用例,使用数组的data缓冲区是最有效的,但是您需要注意一些常用的注意事项。我稍后会详细说明。


但是,首先让我回答你的几个问题并提供一些澄清:

  

buf[:N]为新的数组对象分配内存,长度为N + 1,对吧?

这取决于你的意思"新的数组对象"。无论所涉及的阵列大小如何,都会分配很少的额外内存。

它为新的数组对象(几个字节)分配内存,但它为数组的数据分配额外的内存。相反,它会创建一个"视图"共享原始数组的数据缓冲区。您对y = buf[:N]所做的任何更改也会影响buf

  

buf [:N] .tostring()为新字符串分配内存,并将buf中的字节复制到该字符串中

是的,这是对的。

另一方面,您实际上可以采用相反的方式(字符串到数组)而不分配任何额外的内存:

somestring = 'This could be a big string'
arr = np.frombuffer(buffer(somestring), dtype=np.uint8)

但是,因为python字符串是不可变的,arr将是只读的。


  

有没有办法告诉f.write直接访问" buf"的内存地址?从0到N字节并将它们写入磁盘?

是的!

基本上,您需要:

f.write(buf[:N].data)

这非常有效,适用于任何类文件对象。在这个确切的情况下,它几乎肯定是你想要的。但是,有几点需要注意!

首先请注意,N将位于数组中的项目中,而不是直接以字节为单位。它们在您的示例代码中是等效的(由于dtype=np.int8或任何其他8位数据类型)。

如果你确实想写一些字节,你可以做

f.write(buf.data[:N])

...但切片arr.data缓冲区将分配一个新字符串,因此它在功能上类似于buf[:N].tostring()。无论如何,请注意,对于大多数dtypes,执行f.write(buf[:N].tostring())与执行f.write(buf.data[:N])不同,但两者都会分配一个新字符串。

接下来,numpy数组可以共享数据缓冲区。在您的示例中,您不必担心这一点,但一般情况下,使用somearr.data可能会因此而出现意外。

举个例子:

x = np.arange(10, dtype=np.uint8)
y = x[::2]

现在,yx共享相同的内存缓冲区,但它在内存中不连续(请查看x.flags vs y.flags)。相反,它会在x的内存缓冲区中引用其他每个项(比较x.stridesy.strides)。

如果我们尝试访问y.data,我们会收到错误,告诉我们这不是内存中的连续数组,我们无法为其获取单段缓冲区:

In [5]: y.data
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-54-364eeabf8187> in <module>()
----> 1 y.data

AttributeError: cannot get single-segment buffer for discontiguous array

这是numpy数组有一个tofile方法的原因很大一部分(它也是python&#39; s buffer之前的那个,但是那个&#39}另一个故事)。

tofile会将数组中的数据写入文件而不分配额外的内存。但是,因为它在C级实现,它只适用于真正的file对象,而不适用于类似文件的对象(例如套接字,StringIO等)。

例如:

buf[:N].tofile(f)

但是,这是在C级实现的,只适用于实际的文件对象,而不适用于套接字,StringIO和其他类似文件的对象。

但这确实允许您使用任意数组索引。

buf[someslice].tofile(f)

将创建一个新视图(相同的内存缓冲区),并有效地将其写入磁盘。在您的确切情况下,它会比切片arr.data缓冲区并直接将其写入磁盘稍慢。 如果您更喜欢使用数组索引(而不是字节数),那么ndarray.tofile方法将比f.write(arr.tostring())更有效。