2016-07-26 77 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%的時間)和寫給某些事情的表格。但未來的使用可能會有所不同這也是一些令我驚詫於我自己而不是提供的工廠功能的子類化/構建容器的東西。我不願意介紹一些難以追蹤的錯誤,這些錯誤只會在我忘記半年前做過的事情時纔會出現。 –