2017-06-28 2 views
0

Warum verhält sich numpy.array anders als Pythons Liste und Standard-Arrays, wenn es um Slicen geht? Bitte beachten Sie die folgenden Beispiele:numpy.array Slicing Verhalten

1) Verwenden von Listen: Die Anweisung b = a[1:3] erstellt ein neues Listenobjekt und das Ändern b ändert nicht a.

>>> a = [1,2,3,4] 
>>> b = a[1:3] 
>>> print(b) 
[2, 3] 
>>> b[0] = -17 
>>> print(b) 
[-17, 3] 
>>> print(a) 
[1, 2, 3, 4] 

2) mit array.array: die Aussage b = a[1:3] einmal schafft wieder ein neues Array-Objekt, und b Modifizierung verändert nicht a.

>>> import array 
>>> a = array.array('i', [1,2,3,4]) 
>>> b = a[1:3] 
>>> print(b) 
array('i', [2, 3]) 
>>> b[0] = -17 
>>> print(b) 
array('i', [-17, 3]) 
>>> print(a) 
array('i', [1, 2, 3, 4]) 

3) mit numpy.array: die Anweisung b = a[1:3] die Werte der ursprünglichen Liste zu verweisen scheint, und ändert es auch nicht ändern!

Die Frage ist: Warum ist dieses Verhalten in numpy vorhanden?

Antwort

1

Weil NumPy eine leistungsstarke Datensammlung ist. Damit Python eine neue Liste erstellen kann, muss es eine neue Liste erstellen, alle Zeiger auf jedes Element in der Liste inkrementieren, das Element zur Liste hinzufügen und dann das Segment zurückgeben. NumPy (wahrscheinlich) erhöht einfach den Offset des Startarrays und ändert das Ende des Arrays.

NumPy

Denken Sie an eine NumPy Array als so etwas wie dieses Aufschneiden (ja, das sehr stark vereinfacht ist):

struct array 
{ 
    size_t type_size; 
    size_t length 
    void* start; 
}; 

Wenn Sie C nicht kennen, dann bedeutet im Grunde, dass ein Array kann als eine Adresse zum Speicher gedacht werden, die den Start des Arrays bezeichnet, es speichert die Größe jedes Typs, den es speichert, und dann die Länge des Puffers. Für ein Integer-Array haben wir eine type_size von 4 und in diesem Beispiel eine Länge von 5 (für einen Puffer von 20 Bytes).

Beim Schneiden, anstatt die gesamten Daten zu kopieren, kann NumPy einfach den Start erhöhen und die Größe reduzieren.

array slice(array* array, size_t start, size_t end) 
{ 
    array arr = *array; 
    arr.start = (char*)arr.start + start; 
    arr.length = end - start; 
    return arr; 
} 

Dies ist dramatisch billiger als für eine neue Liste Zuweisen von Speicher und Zuordnen dann (und Erhöhen, ist Python Referenz gezählt) diese Zeiger in die Liste.

Python Slicing

Hier ist ein vereinfachtes Beispiel für Python:

PyObject* slice(PyObject* list, size_t start, size_t end) 
{ 
    size_t length = end - start; 
    PyObject* out = PyList_New(length); 
    for (size_t i = start; size_t i < end; ++i) { 
     PyObject*item = PyList_GetItem(list, i); 
     PyList_Append(&out, i); 
    } 

    return out; 
} 

Beachten Sie, wie viel mehr beteiligt ist das? Und vieles mehr geht unter die Haube.

Rational

Denken Leistung: für NumPy das ursprüngliche Scheibe Verhalten zu haben, muss es eine neue Adresse im Speicher belegt (da die Daten im Speicher angrenzt). Dies würde bedeuten, dass die Daten wahrscheinlich über memcpy() kopiert werden. Das ist teuer: Wenn ich ein Array von 20.000 np.int32 (~ 80 KB) habe, müsste ich all diese Daten in ein neues Array kopieren.Im obigen Slice-Beispiel kopiere ich nur ~ 24 Bytes Speicher (unter der Annahme von 8-Byte size_t und Zeigern).

+1

Vielen Dank für die klare Erklärung. –