6.6. Indizierung und Slicing¶
Ein ndarray wird auf vier Arten adressiert: einzelne Indizes, Slices, boolesche Masken und die Zuweisungsformen jeder davon.
6.6.1. Einzelne Elemente¶
Die Indizierung mit eckigen Klammern gibt den Wert an der angegebenen Position zurück:
a = np.arange(10, dtype=np.uint8)
print(a[0], a[-1]) # 0 9
print(a[1], a[-2]) # 1 8
Negative Indizes zählen vom Ende her, genau wie bei einer Python-list. Ein Index außerhalb des Bereichs löst IndexError aus.
Bei höherrangigen Arrays nimmt jede Achse einen Index. Die Indizes stehen innerhalb eines Satzes von Klammern, durch Kommas getrennt:
m = np.arange(9, dtype=np.uint8).reshape((3, 3))
print(m[1, 1]) # 4
print(m[2, 0]) # 6
Wenn weniger Indizes als Achsen angegeben werden, bleiben die nicht indizierten Achsen unverändert. Das Ergebnis ist ein View mit reduziertem Rang der Quelle:
print(m[0]) # the first row, as a 1-D view of m
6.6.2. Slices¶
Ein Slice start:stop:step gibt einen View des Arrays zurück. Der View teilt sich den zugrunde liegenden Datenpuffer mit der Quelle; das Schreiben durch den View schreibt in die Quelle:
a = np.arange(10, dtype=np.uint8)
v = a[::2] # array([0, 2, 4, 6, 8], dtype=uint8)
v[0] = 99
print(a)
# array([99, 1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=uint8)
Wenn ein unabhängiger Puffer benötigt wird, erzeugt copy() einen solchen explizit.
Slicing erstreckt sich auf natürliche Weise auf höhere Dimensionen. Jede Achse nimmt ihr eigenes Slice:
m = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]], dtype=np.uint8)
m[0] # first row
m[0, :2] # first two elements of row 0
m[:, 0] # column 0 (still 2-D in ulab)
m[-1] # last row
m[::2, ::2] # every other row, every other column
Das Mischen eines Integers (einzelner Index, verwirft die Achse) und eines Slices (behält die Achse) ist erlaubt und ist die übliche Schreibweise für den Zugriff auf eine einzelne Zeile / Spalte.
6.6.3. Boolesche Masken¶
Ein boolesches Array mit derselben Form wie die Quelle wählt Elemente aus, an denen die Maske True ist. Boolesche Indizierung funktioniert derzeit bei 1-D-Arrays; höherrangige Eingaben lösen NotImplementedError aus:
a = np.arange(9, dtype=np.float)
mask = a < 5
print(a[mask])
Ausgabe:
array([0.0, 1.0, 2.0, 3.0, 4.0], dtype=float)
Die Maske ist ein gewöhnliches bool-ndarray, sodass jeder Ausdruck, der ein solches liefert, funktioniert:
b = np.array([4, 4, 4, 3, 3, 3, 13, 13, 13], dtype=np.uint8)
a = np.arange(9, dtype=np.uint8)
print(a[a * a > np.sin(b) * 100.0])
Boolesche Indizierung gibt eine Kopie zurück. Die ausgewählten Elemente liegen an genau den Positionen, an denen die Maske True ist – nicht an einem regelmäßigen Stride durch die Quelle – sodass es keinen Deskriptor gibt, mit dem ein View sie adressieren könnte, und das Ergebnis wird in seinem eigenen Puffer materialisiert.
6.6.4. Indizierung mit Integer-Arrays¶
Das Übergeben einer Liste oder eines Arrays von Indizes in Klammern wählt diese Elemente in einem Schritt heraus:
a = np.array([10, 20, 30, 40, 50], dtype=np.uint8)
a[[0, 2, 4]]
# array([10, 30, 50], dtype=uint8)
Das Ergebnis ist eine Kopie; die ausgewählten Elemente teilen sich keinen Speicher mehr mit der Quelle. Dieselbe Form funktioniert auf der linken Seite einer Zuweisung:
a[[0, 2, 4]] = 0
# array([0, 20, 0, 40, 0], dtype=uint8)
take() (behandelt auf Auswahl und Umsortierung) ist die Funktionsform derselben Operation und akzeptiert ein out=-Schlüsselwort für die allokationsfreie Verwendung in einer Streaming-Schleife.
6.6.5. Slice-Zuweisung¶
Slices und Masken erscheinen sowohl auf der linken als auch auf der rechten Seite einer Zuweisung. Die rechte Seite kann ein Skalar, ein anderes Array oder ein View sein:
m = np.zeros((3, 3), dtype=np.uint8)
m[0] = 1 # whole row 0 set to 1
m[:, 2] = 3 # whole column 2 set to 3
m[1, 1:3] = [7, 8] # row 1, columns 1 and 2
Boolesche Masken auf der linken Seite ersetzen die Elemente, die die Bedingung erfüllen:
a = np.arange(9, dtype=np.uint8)
a[a < 3] = 99
# array([99, 99, 99, 3, 4, 5, 6, 7, 8], dtype=uint8)
a = np.arange(9, dtype=np.uint8)
b = np.array(range(9)) + 12
a[b < 15] = b[b < 15]
# array([12, 13, 14, 3, 4, 5, 6, 7, 8], dtype=uint8)
6.6.6. Warum Slice-Zuweisung auf einer Kamera wichtig ist¶
Slice-Zuweisung schreibt durch ein bereits existierendes Array. Es wird kein neues Array alloziert. Das ist der Unterschied zwischen:
out = a + b # makes a new array the size of a
out = out * 2 # makes another new array
und:
out[:] = a # writes into the existing out
out += b # in place
out *= 2 # in place
Die erste Version fordert von der Kamera RAM im Umfang von zwei frischen Arrays an; die zweite Version fordert nichts an. Auf einem Mikrocontroller mit begrenztem RAM ist dieser Unterschied oft der Unterschied zwischen einem Skript, das komfortabel läuft, und einem, dem der Speicher ausgeht.
Leistung behandelt das Muster im Detail. Die wichtige Regel für den Moment ist, dass Slice-Zuweisung, die In-Place-Arithmetikoperatoren (+=, *=, …) und das out=-Schlüsselwort bei universellen Funktionen die drei Werkzeuge sind, die allokationsfreie Aktualisierungen ermöglichen.