2016-07-26 3 views
1

Ich habe eine ndarray von ASCII-Strings unterschiedlicher Länge. Bisher habe ich dafür dtype=object verwendet. Allerdings hat das Profiling gezeigt, dass dies in meinem Programm tatsächlich ein Flaschenhals ist. Die Verwendung von dtype=np.string_ ist schneller, hat jedoch den Nachteil, dass die eingestellten Werte stillschweigend abgeschnitten werden. Da dies ein perfektes Rezept für schwer zu findende Bugs ist, frage ich mich, ob es eine Möglichkeit gibt, entweder rescale (ich weiß, dass dies im Falle einer vollständigen Neuzuweisung kostspielig sein könnte) das Array oder eine Exception im Falle einer Trunkierung auszulösen?numpy ndarray throw Ausnahme beim Abschneiden der Zeichenfolge

Ich konnte ndarray.__setitem__ nicht ändern, da es ein schreibgeschütztes Attribut ist. Hier ist ein Code zu zeigen, was ich meine:

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 

in Resultierende:

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

*“... eine Ausnahme im Fall der Kürzung erhöhen ? "* Es wäre schön, wenn das Setzen eines Flags dieses Verhalten aktiviert, vielleicht etwas ähnlich wie' numpy.seterr() 'die Handhabung von Gleitkommafehlern steuert. Ich habe noch nie eine solche Flagge gesehen, aber als Verbesserungsanfrage für numpy würde ich es +1 geben. –

+0

@WarrenWeckesser. Ich stimme zu, es könnte auch eine Warnung oder etwas Ähnliches sein. –

Antwort

0

Ich fand eine nicht-flexible Lösung durch Erben von ndarray. Ich werde diese Antwort bis Freitag nicht annehmen, vielleicht kommt jemand auf etwas Besseres.Er erfüllt seine Aufgaben, auch auf Ansichten (zB String (...) [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') 

produziert:

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

Mal sehen, kann ich erklären, was genau wie ll auf

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 los ist besteht aus Zeigern - Zeiger auf Strings, die sich anderswo im Speicher befinden. Ich kann jeden dieser Zeiger ändern, genauso wie ich es in einer Liste tun könnte. In den meisten Fällen verhält sich a1 genau wie eine Liste - außer dass es möglich ist, umzugestalten, und einige andere grundlegende array Dinge zu tun.

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

Aber wenn ich eine string Array

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 

die Werte in den Datenpuffer des Array gespeichert sind. Hier wurde ein Array mit 5 Unicode-Zeichen pro Element (Python3) erstellt (jeweils 5 * 4 Bytes).

Nun, wenn ich ein Element der a2 ersetzen kann ich Abschneiden

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

weil nur 5 der Zeichen des neuen Wertes fit in den zugewiesenen Raum bekommen.

Es gibt also einen Kompromiss aus - schnellere Zugriffsgeschwindigkeit, weil die Bytes in einem festen Array bekannter Größe gespeichert sind, aber Sie können größere Dinge nicht speichern.

könnten Sie mehr Platz pro Element zuordnen:

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 ist ein übliches Werkzeug für Arrays mit String dtypes zu schaffen. Das wartet, bis es die gesamte Datei gelesen hat, bevor die Zeichenfolgenlänge festgelegt wird (zumindest wenn dtype=None verwendet wird). Und die String-Felder sind oft Teil eines Arrays mit mehreren Feldern. Die Zeichenfolgenfelder sind normalerweise Beschriftungen oder IDs, die Sie normalerweise nicht ändern.

Ich kann mir vorstellen, eine Funktion schreiben, die String-Länge gegen den dtype überprüfen würde und einen Fehler auslösen, wenn die Kürzung passieren würde. Aber das wird die Aktion verlangsamen.

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 

aber ist es die zusätzliche Arbeit wert?

+0

Vielen Dank für die Erklärung. Das erklärt die Ursache, aber leider gibt es keine Lösung für mein Problem. Ich habe ein bisschen herumgespielt und eine Lösung gefunden, nicht sehr flexibel, aber leider. –

+0

Welche Art von Flexibilität benötigen Sie? Was machst du mit diesen String-Arrays nach der Konstruktion? Es kann schwierig sein, sicherzustellen, dass Unterklassen beim Durchlaufen verschiedener "numpy" -Funktionen erhalten bleiben. Beachten Sie zum Beispiel, dass 'np.array' den Parameter 'subok' hat. – hpaulj

+0

Ich weiß nicht von diesem Moment an. Im Moment kann man es sich als eine Tabelle vorstellen, die gelesen wird (> 95% der Zeiten) und manchmal geschrieben wird. Die zukünftige Verwendung kann jedoch abweichen. Das ist auch etwas, das mich davor skeptisch macht, den Container selbst zu bilden und zu konstruieren anstatt die vorgesehene Fabrikfunktion. Ich würde es hassen, einige schwierige Bugs einzuführen, die erst auftauchen werden, wenn ich vergessen habe, was ich vor einem halben Jahr gemacht habe. –