6.2. Das ndarray

Das ndarray ist der Typ, der numerische Daten in numpy hält. Es ist zwei Dinge in einem: ein einzelner gepackter Datenblock und ein kleiner Deskriptor vor diesem Block, der angibt, wie er zu lesen ist.

6.2.1. Innerhalb der Box

Der Datenblock hält jedes Element des Arrays Ende an Ende, ohne irgendetwas Zusätzliches dazwischen. Jedes Element belegt dieselbe Anzahl von Bytes – eines für ein Array von uint8-Werten, zwei für uint16, vier für float. Ein 256-elementiges uint8-Array sind genau 256 Bytes Daten; dieselben 256 Zahlen in einer Python-list belegen ein Kilobyte – ein 32-Bit-Slot pro Element, unabhängig davon, wie wenige Bits der Wert tatsächlich benötigt.

Der Deskriptor erfasst, was der Block bedeutet. Fünf Werte genügen, um jedes rechteckige Array zu beschreiben, egal wie viele Dimensionen es hat:

  • dtype – der Elementtyp. Bestimmt, wie viele Bytes jedes Element belegt und welchen Wertebereich es halten kann (behandelt auf Dtypes).

  • itemsize – die Byte-Breite eines Elements, abgeleitet vom dtype.

  • ndim – die Anzahl der Dimensionen (1 für einen Vektor, 2 für eine Matrix, 3 für ein Volumen, bis zu 4).

  • shape – die Größe entlang jeder Dimension als Tupel.

  • strides – wie man durch den Datenblock schreitet, um jede Achse zu durchlaufen. Behandelt auf Form und Strides.

Das ist alles. Jeder schnelle Pfad in numpy – Arithmetik, Reduktionen, Broadcasting, Slicing – arbeitet direkt aus diesen fünf Werten plus dem Datenzeiger, ohne Python-Overhead pro Element.

6.2.2. Was das Design einbringt

Drei Eigenschaften ergeben sich aus „gepackter Block + kleiner Deskriptor“ und definieren, wie sich der Rest des Abschnitts verhält.

Elementweise Mathematik läuft als ein einziger Aufruf. a + b zwischen zwei Arrays übereinstimmender Form addiert die beiden Puffer und schreibt einen dritten, alles innerhalb eines einzigen Bibliotheksaufrufs. np.sin(a) tut dasselbe für den Sinus jedes Elements. Die arithmetischen, Vergleichs- und bitweisen Operatoren arbeiten alle auf diese Weise.

Dieselben Daten anders zu betrachten ist kostenlos. Das Anfordern einer Teilregion eines Arrays oder derselben Daten in einer anderen Form angeordnet bewegt keine Bytes. Die Operation gibt einen neuen Deskriptor zurück, der auf denselben Datenblock zeigt. Das Ergebnis wird View genannt – ein zweites Fenster auf denselben zugrunde liegenden Puffer. Das Schreiben durch einen View schreibt in die Quelle.

Gemischte Formen funktionieren trotzdem. Ein kürzeres Array gegen ein längeres, eine Zeile gegen eine Matrix, eine Spalte gegen eine Zeile – numpy richtet sie durch Broadcasting aus, eine kleine Menge von Regeln, die entscheiden, welche kurze Achse sich streckt, um der langen zu entsprechen. Die Streckung ist virtuell; keine Daten werden dupliziert.

6.2.3. Was das Design kostet

Zwei Einschränkungen folgen aus demselben Design.

Jedes Element hat denselben Typ. Eine Liste kann einen int neben einem str neben einer Liste von drei weiteren int-Werten halten; ein ndarray kann das nicht. Der dtype ist zur Konstruktionszeit festgelegt. Die Seite Dtypes behandelt die kleine Menge von Typen, die numpy unterstützt, und die Regeln, die sich aus der Festlegung eines Typs ergeben.

Das Vergrößern eines Arrays ist nicht kostenlos. Eine Liste behält freie Slots am Ende und unterstützt .append kostengünstig. Ein ndarray ist genau so groß, wie es sein muss; das Anhängen würde bedeuten, einen neuen, größeren Puffer zu allokieren und den alten Inhalt hineinzukopieren. Es gibt absichtlich keine append()-Methode. Das richtige Muster auf der Kamera ist, das Ziel in seiner endgültigen Größe vorab zu allokieren und es zu füllen; Leistung behandelt die Technik.

Mit einem gepackten typisierten Puffer für die Daten, einem kleinen Deskriptor für die Metadaten und drei Verhaltensgarantien (schnelle elementweise Mathematik, kopierfreie alternative Views derselben Daten und Formen, die broadcasten) ist das ndarray das Fundament, auf dem der Rest des Kapitels ruht. Wie ein Array tatsächlich entsteht – aus einem Literal, aus einer vorgefüllten Allokation, aus einem Peripheriepuffer – ist die nächste praktische Frage.