如何在不复制的情况下将numpy数组转换为bytes/BytesIO?

ifmq2ha2  于 2023-08-05  发布在  其他
关注(0)|答案(1)|浏览(98)

我想使用boto3包上传一个numpy数组到S3,该包需要一个bytes对象。我想将这个numpy数组转换为字节,但由于内存限制,没有任何复制。我尝试的东西不工作,因为他们创建副本:

numpy似乎曾经提供numpy.ndarray.getbuffer,但在后来的版本中被弃用。
有没有一种方法可以创建一个字节视图而不复制?

ggazkfy8

ggazkfy81#

您可以利用ctypes模块创建指向数据数组的指针,并将其转换为字节形式。

import ctypes

import numpy as np

# generate the test array 
size = 0x10
dtype = np.short
bsize = 2 # size of a single np.short in bytes, set for the data type you want to upload
arr = np.arange(size, dtype=dtype)

# create a pointer to the block of memory that the array lives in, cast to char type. Note that (size*bsize) _must_ be in parenthesis for the code to run correctly.
memory_block = (ctypes.c_char*(size*bsize)).from_address(arr.ctypes.data)
print(memory_block.raw)
# b'\x00\x00\x01\x00\x02\x00\x03\x00\x04\x00\x05\x00\x06\x00\x07\x00\x08\x00\t\x00\n\x00\x0b\x00\x0c\x00\r\x00\x0e\x00\x0f\x00'

# mutate the array and check the contents at the pointer
arr[0] = 255.
print(memory_block.raw)
# b'\xff\x00\x01\x00\x02\x00\x03\x00\x04\x00\x05\x00\x06\x00\x07\x00\x08\x00\t\x00\n\x00\x0b\x00\x0c\x00\r\x00\x0e\x00\x0f\x00'

字符串
至少,这似乎满足了你在对问题的评论中提出的测试。(也就是说,如果我改变了数组,我对它的看法会改变吗?).
不过,这里有几件事需要注意。第一,python bytes对象是不可变的,这意味着如果一个对象被分配给一个变量,就会产生一个副本。

y = memory_block.raw
print(y[:2])
# b'\xff\x00'
arr[0] = 127
print(y[:2])
# b'\xff\x00'


第二,boto3似乎想要一个类似File的对象,至少从版本1.28.1的源代码来看是这样。调用bio = BytesIO(memory_block.raw)会产生一个副本,这意味着我们又回到了上传的起点。

上传类

下面的ArrayUploader类实现了一些基本的IO方法(readseektell)。当read被调用时,数据 * 仍然可以从底层内存blob中复制 *,这意味着余量仍然是限制因素。但是,如果设置了读取的大小,则一次仅从内存blob复制这么多数据。boto3如何处理从IO对象读取的大小,我不能告诉你。

import ctypes
import re
from io import IOBase

import numpy as np

class ArrayUploader(IOBase):
    # set this up as a child of IOBase because boto3 wants an object
    # with a read method. 
    def __init__(self, array):
        # get the number of bytes from the name of the data type
        # this is a kludge; make sure it works for your case
        dbits = re.search('\d+', str(np.dtype(array.dtype))).group(0)
        dbytes = int(dbits) // 8
        self.nbytes = array.size * dbytes
        self.bufferview = (ctypes.c_char*(self.nbytes)).from_address(array.ctypes.data)
        self._pos = 0

    def tell(self):
        return self._pos

    def seek(self, pos):
        self._pos = pos

    def read(self, size=-1):
        if size == -1:
            return self.bufferview.raw[self._pos:]
        old = self._pos
        self._pos += size
        return self.bufferview.raw[old:self._pos]
    

# generate the test array 
size = 0x10
dtype = np.short
arr = np.arange(size, dtype=dtype)

# initialize our uploader object
arrayuploader = ArrayUploader(arr)

# read some data out
print(x:=arrayuploader.read(8))
# b'\x00\x00\x01\x00\x02\x00\x03\x00'

# mutate the array, reread the same data
arr[0] = 127
arrayuploader.seek(0)
print(y:=arrayuploader.read(8))
# b'\x7f\x00\x01\x00\x02\x00\x03\x00'

# has x changed with the original array?
print(x == y)
# False

相关问题