2016-07-26 89 views
1

我有一个不同长度的ascii字符串ndarray。到现在为止,我使用dtype=object。然而,分析表明,这实际上是我的计划中的一个瓶颈。使用dtype=np.string_速度更快,但它有一个缺点,它会静默截断设置值。由于这是一个很难找到错误的完美配方,我想知道是否有可能重新调整(我知道这可能是昂贵的整个重新分配的情况下)数组或在截断的情况下引发异常?numpy ndarray在截断字符串时抛出异常

我无法更改ndarray.__setitem__,因为它是只读属性。下面是一些代码来证明我的意思:

import numpy as np 


def Foo(vec): 
    vec[1] = 'FAIL' 

    print('{:6s}: {}'.format(str(vec.dtype), vec)) 


VALUES = ['OK', 'OK', 'OK'] 

Foo(np.array(VALUES, dtype=object)) # Slow but it works 
Foo(np.array(VALUES, dtype=np.string_)) # Fast but may fail silently 

结果造成:

object: ['OK' 'FAIL' 'OK'] 
|S2 : [b'OK' b'FA' b'OK'] 
+0

*” ......在截断的情况下,产生异常?“*如果设置某个标志启用了这种行为,这可能会很好,可能类似于'numpy.seterr()'控制浮点错误的处理方式。我从来没有见过这样的旗帜,但作为一个增强的numpy请求,我会给它一个+1。 –

+0

@WarrenWeckesser。我同意,它也可能是一个警告或类似的东西。 –

回答

0

我从ndarray继承了一个非灵活的解决方案。直到周五,我都不会接受这个答案,也许有人想出更好的东西。它履行其职责,即使在视图(例如字符串数组(...)[1:4]。)

import numpy as np 

class StringArray(np.ndarray): 
    def __new__(cls, val): 
     field_length = max(map(len, val)) 
     # Could also be <U for unicode 
     vec = super().__new__(cls, len(val), dtype='|S' + str(field_length)) 
     vec[:] = val[:] 
     return vec 

    def __setitem__(self, key, val): 
     if isinstance(val, (list, tuple, nd.array)): 
      if max(map(len, val)) > self.dtype.itemsize: 
       raise ValueError('Itemsize too big') 
     elif isinstance(val, str): 
      if len(val) > self.dtype.itemsize: 
       raise ValueError('Itemsize too big') 
     else: 
      raise ValueError('Unknown type') 
     super().__setitem__(key, val) 


val = StringArray(['a', 'ab', 'abc']) 
print(val) 
val[0] = 'xy' 
print(val) 
try: 
    val[0] = 'xyze' 
except ValueError: 
    print('Catch') 

try: 
    val[1:2] = ['xyze', 'sd'] 
except ValueError: 
    print('Catch') 

生产:

[b'a' b'ab' b'abc'] 
[b'xy' b'ab' b'abc'] 
Catch 
Catch 
1

让我们看看我能解释这是怎么回事

In [32]: ll=['one','two','three'] 
In [33]: a1=np.array(ll,dtype=object) 
In [34]: a1 
Out[34]: array(['one', 'two', 'three'], dtype=object) 
In [35]: a1[1]='eleven' 
In [36]: a1 
Out[36]: array(['one', 'eleven', 'three'], dtype=object) 

a1就像ll由指针 - 指向驻留在内存中其他位置的字符串的指针。我可以改变任何这些指针,就像我可以在列表中一样。在大多数方面a1的行为就像一个列表 - 除了它可以重塑,并做一些其他基本的事情。

In [37]: a1.reshape(3,1) 
Out[37]: 
array([['one'], 
     ['eleven'], 
     ['three']], dtype=object) 

但是,如果我做一个string阵列

In [38]: a2=np.array(ll) 
In [39]: a2 
Out[39]: 
array(['one', 'two', 'three'], 
     dtype='<U5') 
In [42]: a1.itemsize 
Out[42]: 4 
In [43]: a2.itemsize 
Out[43]: 20 

的值存储在阵列的数据缓冲。这里它创建了一个数组,每个元素有5个Unicode字符(Python3)(每个字符5 * 4字节)。

现在,如果我取代的a2的元素,我可以得到截断

In [44]: a2[1]='eleven' 
In [45]: a2 
Out[45]: 
array(['one', 'eleve', 'three'], 
     dtype='<U5') 

,因为只有5个新值超出所分配的空间的字符。

所以有一个折衷 - 访问速度更快,因为字节存储在固定的已知大小的数组中,但不能存储更大的东西。

你可以每个元素分配更多的空间:

In [46]: a3=np.array(ll,dtype='|U10') 
In [47]: a3 
Out[47]: 
array(['one', 'two', 'three'], 
     dtype='<U10') 
In [48]: a3[1]='eleven' 
In [49]: a3 
Out[49]: 
array(['one', 'eleven', 'three'], 
     dtype='<U10') 

genfromtxt是一个用于创建与字符串数组dtypes的常用工具。在设置字符串长度之前(至少在使用dtype=None时),它会等待它读取所有文件。字符串字段通常是多字段结构化数组的一部分。字符串字段通常是标签或ID,而不是您经常更改的内容。

我可以想象写一个函数,可以检查字符串长度对dtype和提出一个错误,如果截断会发生。但是这会减缓行动。

def foo(A, i, astr): 
    if A.itemsize/4<len(astr): 
     raise ValueError('too long str') 
    A[i] = astr 

In [69]: foo(a2,1,'four') 
In [70]: a2 
Out[70]: 
array(['one', 'four', 'three'], 
     dtype='<U5') 
In [72]: foo(a2,1,'eleven') 
... 
ValueError: too long str 

但它值得额外的工作?

+0

谢谢你的解释。这阐述了原因,但不幸的是,并没有给我解决我的问题。我玩了一下,发现了各种解决方案,但我很害怕,但不是很灵活。 –

+0

你需要什么样的灵活性?在构建完这些字符串数组后,你在做什么?确保在传递各种'numpy'函数时保留子类是非常棘手的。请注意,例如'np.array'具有'subok'参数。 – hpaulj

+0

我不知道这一刻。目前你可以把它看作是一张阅读(> 95%的时间)和写给某些事情的表格。但未来的使用可能会有所不同这也是一些令我惊诧于我自己而不是提供的工厂功能的子类化/构建容器的东西。我不愿意介绍一些难以追踪的错误,这些错误只会在我忘记半年前做过的事情时才会出现。 –