ndarray.resize:正确传递refcheck参数值

2 投票
2 回答
1008 浏览
提问于 2025-04-17 17:59

和很多人一样,我的情况是有一个类,它收集了大量的数据,并提供一个方法可以把这些数据返回为一个numpy数组。(即使在返回数组后,额外的数据仍然可以继续流入。)因为创建这个数组是个耗时的操作,所以我想在必要的时候才创建它,并尽可能高效地进行(具体来说,就是在可能的情况下就地添加数据)。

为此,我查阅了一些关于ndarray.resize()方法和refcheck参数的资料。我了解到,只有在“你确定没有和其他Python对象共享这个数组的内存”时,refcheck应该设置为False。

问题是我不太确定。有时候我共享了,有时候没有。如果refcheck失败时抛出错误我也没问题(我可以捕获这个错误,然后创建一个新的副本),但我希望它只在有“真正的”外部引用时才失败,忽略那些我知道是安全的引用。

这里有一个简化的例子:

import numpy as np

def array_append(arr, values, refcheck = True):
    added_len = len(values)
    if added_len == 0:
        return arr
    old_len = len(arr)
    new_len = old_len + added_len
    arr.resize(new_len, refcheck = refcheck)
    arr[old_len:] = values
    return arr

class DataCollector(object):

    def __init__(self):
        self._new_data = []
        self._arr = np.array([])

    def add_data(self, data):
        self._new_data.append(data)

    def get_data_as_array(self):
        self._flush()
        return self._arr

    def _flush(self):
        if not self._new_data:
            return
#        self._arr = self._append1()
#        self._arr = self._append2()
        self._arr = self._append3()
        self._new_data = []

    def _append1(self):
        # always raises an error, because there are at least 2 refs:
        # self._arr and local variable 'arr' in array_append()
        return array_append(self._arr, self._new_data, refcheck = True)

    def _append2(self):
        # Does not raise an error, but unsafe in case there are other
        # references to self._arr
        return array_append(self._arr, self._new_data, refcheck = False)

    def _append3(self):
        # "inline" version: works if there are no other references
        # to self._arr, but raises an error if there are.
        added_len = len(self._new_data)
        old_len = len(self._arr)
        self._arr.resize(old_len + added_len, refcheck = True)
        self._arr[old_len:] = self._new_data
        return self._arr

dc = DataCollector()
dc.add_data(0)
dc.add_data(1)
print dc.get_data_as_array()
dc.add_data(2)
print dc.get_data_as_array()
x = dc.get_data_as_array()  # create an external reference
print x.shape
for i in xrange(5000):
    dc.add_data(999)
print dc.get_data_as_array()
print x.shape

问题:

  1. 有没有更好的(更快的)方法来实现我想做的事情(逐步创建numpy数组)?
  2. 有没有办法告诉resize()方法:“执行refcheck,但忽略我知道是安全的那个引用(或n个引用)”?(这样就能解决_append1()总是失败的问题)

2 个回答

1

我将使用 array.array() 来收集数据:

import array
a = array.array("d")
for i in xrange(100):
    a.append(i*2)

每次你想用收集到的数据进行计算时,可以通过 numpy.frombuffer 将它转换成 numpy.ndarray

b = np.frombuffer(a, dtype=float)
print np.mean(b)

ba 会共享数据内存,所以这个转换过程非常快。

1

resize 方法有两个主要问题。第一个问题是,当用户调用 get_data_as_array 时,你返回了 self._arr 的引用。这样一来,resize 的行为就会根据你的实现方式而有所不同。它可能会修改用户得到的数组,比如用户调用 a.shape 时,数组的形状会不可预测地改变。或者,它可能会损坏那个数组,让它指向错误的内存。你可以通过让 get_data_as_array 始终返回 self._arr.copy() 来解决这个问题,但这就引出了第二个问题。resize 实际上并不是很高效。一般来说,每次调用 resize 时,它都需要分配新的内存并复制数组,以便扩展数组。而且现在你每次想返回数组给用户时,都需要复制一遍。

另一种方法是设计你自己的 动态数组,大致可以这样实现:

class DynamicArray(object):

    _data = np.empty(1)
    data = _data[:0]
    len = 0
    scale_factor = 2

    def append(self, values):
        old_data = len(self.data)
        total_data = len(values) + old_data
        total_storage = len(self._data)
        if total_storage < total_data:
            while total_storage < total_data:
                total_storage = np.ceil(total_storage * self.scale_factor)
            self._data = np.empty(total_storage)
            self._data[:old_data] = self.data

        self._data[old_data:total_data] = values
        self.data = self._data[:total_data]

这样做应该会非常快,因为你只需要扩展数组 log(N) 次,并且最多使用 2*N-1 的存储空间,其中 N 是数组的最大大小。除了扩展数组之外,你只是在创建 _data 的视图,这不涉及任何复制,应该是常数时间操作。

希望这些信息对你有帮助。

撰写回答