Buffer-Protokoll¶
Bestimmte Objekte in Python bieten Zugriff auf ein zugrunde liegendes Speicherarray oder einen Puffer. Zu diesen Objekten gehören die integrierten Typen bytes und bytearray sowie einige Erweiterungstypen wie array.array. Drittanbieterbibliotheken können eigene Typen für spezielle Zwecke definieren, z. B. für die Bildverarbeitung oder numerische Analysen.
Während jeder dieser Typen seine eigene Semantik hat, teilen sie die gemeinsame Eigenschaft, dass sie durch einen möglicherweise großen Speicherpuffer gesichert sind. Es ist daher in einigen Situationen wünschenswert, direkt und ohne Zwischenkopien auf diesen Puffer zuzugreifen.
Python stellt diese Funktionalität auf C- und Python-Ebene in Form des Buffer-Protokolls bereit. Dieses Protokoll hat zwei Seiten
Auf der Produzentenseite kann ein Typ eine "Buffer-Schnittstelle" exportieren, die es Objekten dieses Typs ermöglicht, Informationen über ihren zugrunde liegenden Puffer preiszugeben. Diese Schnittstelle wird im Abschnitt Buffer-Objektstrukturen beschrieben; für Python siehe Emulation von Puffer-Typen.
Auf der Konsumentenseite stehen mehrere Möglichkeiten zur Verfügung, um einen Zeiger auf die rohen zugrunde liegenden Daten eines Objekts zu erhalten (z. B. als Methodenparameter). Für Python siehe
memoryview.
Einfache Objekte wie bytes und bytearray geben ihren zugrunde liegenden Puffer in byte-orientierter Form aus. Andere Formen sind möglich; zum Beispiel können die von einem array.array exponierten Elemente mehrbyte-Werte sein.
Ein Beispiel für einen Konsumenten der Buffer-Schnittstelle ist die write()-Methode von Datei-Objekten: Jedes Objekt, das eine Byte-Sequenz über die Buffer-Schnittstelle exportieren kann, kann in eine Datei geschrieben werden. Während write() nur Lesezugriff auf den internen Inhalt des übergebenen Objekts benötigt, benötigen andere Methoden wie readinto() Schreibzugriff auf den Inhalt ihres Arguments. Die Buffer-Schnittstelle ermöglicht es Objekten, den Export von Lese-/Schreib- und schreibgeschützten Puffern selektiv zu erlauben oder abzulehnen.
Es gibt zwei Möglichkeiten für einen Konsumenten der Buffer-Schnittstelle, einen Puffer über ein Zielobjekt zu erwerben
Rufen Sie
PyObject_GetBuffer()mit den richtigen Parametern auf;Rufen Sie
PyArg_ParseTuple()(oder eine seiner Geschwisterfunktionen) mit einem dery*,w*oders*Formatcodes auf.
In beiden Fällen muss PyBuffer_Release() aufgerufen werden, wenn der Puffer nicht mehr benötigt wird. Andernfalls kann es zu verschiedenen Problemen wie Ressourcenlecks kommen.
Hinzugefügt in Version 3.12: Das Buffer-Protokoll ist jetzt in Python zugänglich, siehe Emulation von Puffer-Typen und memoryview.
Buffer-Struktur¶
Buffer-Strukturen (oder einfach "Puffer") sind nützlich, um Binärdaten aus einem anderen Objekt für den Python-Programmierer bereitzustellen. Sie können auch als Mechanismus für Zero-Copy-Slicing verwendet werden. Durch ihre Fähigkeit, einen Speicherblock zu referenzieren, ist es möglich, beliebige Daten dem Python-Programmierer sehr einfach zugänglich zu machen. Der Speicher kann ein großes, konstantes Array in einer C-Erweiterung sein, ein roher Speicherblock zur Manipulation, bevor er an eine Betriebssystembibliothek übergeben wird, oder er kann zum Weitergeben strukturierter Daten in seinem nativen In-Memory-Format verwendet werden.
Im Gegensatz zu den meisten Datentypen, die vom Python-Interpreter bereitgestellt werden, sind Puffer keine PyObject-Zeiger, sondern einfache C-Strukturen. Dies ermöglicht ihre einfache Erstellung und Kopie. Wenn ein generischer Wrapper um einen Puffer benötigt wird, kann ein memoryview-Objekt erstellt werden.
Für kurze Anweisungen, wie man ein exportierendes Objekt schreibt, siehe Buffer-Objektstrukturen. Zum Erhalten eines Puffers siehe PyObject_GetBuffer().
-
type Py_buffer¶
- Teil der Stable ABI (einschließlich aller Member) seit Version 3.11.
-
void *buf¶
Ein Zeiger auf den Anfang der logischen Struktur, die durch die Pufferfelder beschrieben wird. Dies kann jede Position innerhalb des zugrunde liegenden physischen Speicherblocks des Exporteurs sein. Zum Beispiel kann bei negativen
stridesder Wert auf das Ende des Speicherblocks zeigen.Für kontinuierliche Arrays zeigt der Wert auf den Anfang des Speicherblocks.
-
PyObject *obj¶
Eine neue Referenz auf das exportierende Objekt. Die Referenz gehört dem Konsumenten und wird automatisch freigegeben (d. h. der Referenzzähler wird dekrementiert) und auf
NULLgesetzt vonPyBuffer_Release(). Das Feld ist das Äquivalent zum Rückgabewert jeder Standard-C-API-Funktion.Als Sonderfall sind für temporäre Puffer, die von
PyMemoryView_FromBuffer()oderPyBuffer_FillInfo()umwickelt werden, die FelderNULL. Im Allgemeinen dürfen exportierende Objekte dieses Schema NICHT verwenden.
-
Py_ssize_t len¶
product(shape) * itemsize. Für kontinuierliche Arrays ist dies die Länge des zugrunde liegenden Speicherblocks. Für nicht-kontinuierliche Arrays ist es die Länge, die die logische Struktur hätte, wenn sie in eine kontinuierliche Darstellung kopiert würde.Der Zugriff auf
((char *)buf)[0] bis ((char *)buf)[len-1]ist nur gültig, wenn der Puffer durch eine Anfrage erhalten wurde, die Kontinuität garantiert. In den meisten Fällen ist eine solche AnfragePyBUF_SIMPLEoderPyBUF_WRITABLE.
-
int readonly¶
Eine Angabe, ob der Puffer schreibgeschützt ist. Dieses Feld wird durch das Flag
PyBUF_WRITABLEgesteuert.
-
Py_ssize_t itemsize¶
Elementgröße in Bytes eines einzelnen Elements. Gleicht dem Wert von
struct.calcsize(), aufgerufen auf Nicht-NULLformat-Werten.Wichtige Ausnahme: Wenn ein Konsument einen Puffer ohne das Flag
PyBUF_FORMATanfordert, wirdformataufNULLgesetzt, aberitemsizehat immer noch den Wert für das ursprüngliche Format.Wenn
shapevorhanden ist, gilt die Gleichheitproduct(shape) * itemsize == lenweiterhin und der Konsument kannitemsizeverwenden, um den Puffer zu navigieren.Wenn
shapeaufgrund einer Anforderung vonPyBUF_SIMPLEoderPyBUF_WRITABLENULList, muss der Konsumentitemsizeignorieren und annehmen, dassitemsize == 1.
-
char *format¶
Ein NULL-terminierter String in der Syntax des
struct-Moduls, der den Inhalt eines einzelnen Elements beschreibt. Wenn diesNULList, wird"B"(vorzeichenlose Bytes) angenommen.Dieses Feld wird durch das Flag
PyBUF_FORMATgesteuert.
-
int ndim¶
Die Anzahl der Dimensionen, die der Speicher als n-dimensionales Array darstellt. Wenn es
0ist, zeigtbufauf ein einzelnes Element, das einen Skalar darstellt. In diesem Fall MÜSSENshape,stridesundsuboffsetsNULLsein. Die maximale Anzahl von Dimensionen wird durchPyBUF_MAX_NDIMangegeben.
-
Py_ssize_t *shape¶
Ein Array von
Py_ssize_tder Längendim, das die Form des Speichers als n-dimensionales Array angibt. Beachten Sie, dassshape[0] * ... * shape[ndim-1] * itemsizegleichlensein MUSS.Formwerte sind auf
shape[n] >= 0beschränkt. Der Fallshape[n] == 0erfordert besondere Aufmerksamkeit. Weitere Informationen finden Sie unter komplexe Arrays.Das Formular-Array ist für den Konsumenten schreibgeschützt.
-
Py_ssize_t *strides¶
Ein Array von
Py_ssize_tder Längendim, das die Anzahl der Bytes angibt, die übersprungen werden müssen, um zu einem neuen Element in jeder Dimension zu gelangen.Stride-Werte können beliebige ganze Zahlen sein. Für reguläre Arrays sind Stride-Werte normalerweise positiv, aber ein Konsument MUSS den Fall
strides[n] <= 0handhaben können. Weitere Informationen finden Sie unter komplexe Arrays.Das Stride-Array ist für den Konsumenten schreibgeschützt.
-
Py_ssize_t *suboffsets¶
Ein Array von
Py_ssize_tder Längendim. Wennsuboffsets[n] >= 0, sind die entlang der n-ten Dimension gespeicherten Werte Zeiger und der Suboffset-Wert gibt an, wie viele Bytes nach der Dereferenzierung zu jedem Zeiger addiert werden müssen. Ein negativer Suboffset-Wert bedeutet, dass keine Dereferenzierung erfolgen soll (Striding in einem kontinuierlichen Speicherblock).Wenn alle Suboffsets negativ sind (d. h. keine Dereferenzierung erforderlich ist), muss dieses Feld
NULLsein (der Standardwert).Diese Art der Array-Darstellung wird von der Python Imaging Library (PIL) verwendet. Weitere Informationen zum Zugriff auf Elemente eines solchen Arrays finden Sie unter komplexe Arrays.
Das Suboffsets-Array ist für den Konsumenten schreibgeschützt.
-
void *internal¶
Dies ist zur internen Verwendung durch das exportierende Objekt bestimmt. Zum Beispiel könnte dies vom Exporteur als Integer neu gecastet und zur Speicherung von Flags verwendet werden, die angeben, ob die Arrays shape, strides und suboffsets freigegeben werden müssen, wenn der Puffer freigegeben wird. Der Konsument darf diesen Wert NICHT ändern.
-
void *buf¶
Konstanten
-
PyBUF_MAX_NDIM¶
Die maximale Anzahl von Dimensionen, die der Speicher darstellt. Exporteure MÜSSEN dieses Limit einhalten, Konsumenten von mehrdimensionalen Puffern SOLLTEN bis zu
PyBUF_MAX_NDIMDimensionen verarbeiten können. Derzeit auf 64 gesetzt.
Buffer-Anforderungstypen¶
Puffer werden normalerweise erhalten, indem eine Pufferanforderung über PyObject_GetBuffer() an ein exportierendes Objekt gesendet wird. Da die Komplexität der logischen Struktur des Speichers drastisch variieren kann, verwendet der Konsument das Argument flags, um den genauen Puffer-Typ anzugeben, den er verarbeiten kann.
Alle Felder von Py_buffer werden eindeutig durch den Anforderungstyp definiert.
Anforderungsunabhängige Felder¶
Die folgenden Felder werden nicht von flags beeinflusst und müssen immer mit den korrekten Werten gefüllt werden: obj, buf, len, itemsize, ndim.
readonly, format¶
- PyBUF_WRITABLE¶
Steuert das Feld
readonly. Wenn gesetzt, MUSS der Exporteur einen beschreibbaren Puffer bereitstellen oder einen Fehler melden. Andernfalls KANN der Exporteur entweder einen schreibgeschützten oder einen beschreibbaren Puffer bereitstellen, aber die Wahl MUSS für alle Konsumenten konsistent sein. Zum Beispiel kann PyBUF_SIMPLE | PyBUF_WRITABLE verwendet werden, um einen einfachen beschreibbaren Puffer anzufordern.
PyBUF_WRITABLE kann mit jedem der Flags im nächsten Abschnitt verknüpft werden. Da PyBUF_SIMPLE als 0 definiert ist, kann PyBUF_WRITABLE als eigenständiges Flag verwendet werden, um einen einfachen beschreibbaren Puffer anzufordern.
PyBUF_FORMAT muss mit jedem der Flags außer PyBUF_SIMPLE verknüpft werden, da letzteres bereits das Format B (vorzeichenlose Bytes) impliziert. PyBUF_FORMAT kann nicht allein verwendet werden.
shape, strides, suboffsets¶
Die Flags, die die logische Struktur des Speichers steuern, sind in absteigender Reihenfolge der Komplexität aufgeführt. Beachten Sie, dass jedes Flag alle Bits der darunter liegenden Flags enthält.
Anforderung |
shape |
strides |
suboffsets |
|---|---|---|---|
|
ja |
ja |
falls erforderlich |
|
ja |
ja |
NULL |
|
ja |
NULL |
NULL |
|
NULL |
NULL |
NULL |
Kontinuitätsanforderungen¶
C- oder Fortran-Kontinuität kann explizit angefordert werden, mit und ohne Stride-Informationen. Ohne Stride-Informationen muss der Puffer C-kontinuierlich sein.
Anforderung |
shape |
strides |
suboffsets |
contig |
|---|---|---|---|---|
|
ja |
ja |
NULL |
C |
|
ja |
ja |
NULL |
F |
|
ja |
ja |
NULL |
C oder F |
ja |
NULL |
NULL |
C |
Verbundanforderungen¶
Alle möglichen Anforderungen sind vollständig durch eine Kombination der Flags im vorherigen Abschnitt definiert. Zur Vereinfachung stellt das Buffer-Protokoll häufig verwendete Kombinationen als einzelne Flags bereit.
In der folgenden Tabelle steht U für undefinierte Kontinuität. Der Konsument müsste PyBuffer_IsContiguous() aufrufen, um die Kontinuität zu ermitteln.
Anforderung |
shape |
strides |
suboffsets |
contig |
readonly |
format |
|---|---|---|---|---|---|---|
|
ja |
ja |
falls erforderlich |
U |
0 |
ja |
|
ja |
ja |
falls erforderlich |
U |
1 oder 0 |
ja |
|
ja |
ja |
NULL |
U |
0 |
ja |
|
ja |
ja |
NULL |
U |
1 oder 0 |
ja |
|
ja |
ja |
NULL |
U |
0 |
NULL |
|
ja |
ja |
NULL |
U |
1 oder 0 |
NULL |
|
ja |
NULL |
NULL |
C |
0 |
NULL |
|
ja |
NULL |
NULL |
C |
1 oder 0 |
NULL |
Komplexe Arrays¶
NumPy-Stil: Shape und Strides¶
Die logische Struktur von Arrays im NumPy-Stil wird durch itemsize, ndim, shape und strides definiert.
Wenn ndim == 0, wird die von buf angezeigte Speicheradresse als Skalar der Größe itemsize interpretiert. In diesem Fall sind sowohl shape als auch strides NULL.
Wenn strides NULL ist, wird das Array als Standard-n-dimensionale C-Array interpretiert. Andernfalls muss der Verbraucher wie folgt auf ein n-dimensionales Array zugreifen
ptr = (char *)buf + indices[0] * strides[0] + ... + indices[n-1] * strides[n-1];
item = *((typeof(item) *)ptr);
Wie oben erwähnt, kann buf auf jede beliebige Stelle innerhalb des tatsächlichen Speicherblocks zeigen. Ein Exporteur kann die Gültigkeit eines Puffers mit dieser Funktion überprüfen
def verify_structure(memlen, itemsize, ndim, shape, strides, offset):
"""Verify that the parameters represent a valid array within
the bounds of the allocated memory:
char *mem: start of the physical memory block
memlen: length of the physical memory block
offset: (char *)buf - mem
"""
if offset % itemsize:
return False
if offset < 0 or offset+itemsize > memlen:
return False
if any(v % itemsize for v in strides):
return False
if ndim <= 0:
return ndim == 0 and not shape and not strides
if 0 in shape:
return True
imin = sum(strides[j]*(shape[j]-1) for j in range(ndim)
if strides[j] <= 0)
imax = sum(strides[j]*(shape[j]-1) for j in range(ndim)
if strides[j] > 0)
return 0 <= offset+imin and offset+imax+itemsize <= memlen
PIL-Stil: Shape, Strides und Suboffsets¶
Zusätzlich zu den regulären Elementen können PIL-artige Arrays Zeiger enthalten, denen gefolgt werden muss, um zum nächsten Element einer Dimension zu gelangen. Zum Beispiel kann das reguläre dreidimensionale C-Array char v[2][2][3] auch als ein Array von 2 Zeigern auf 2 zweidimensionale Arrays angesehen werden: char (*v[2])[2][3]. In der Suboffset-Darstellung können diese beiden Zeiger am Anfang von buf eingebettet sein und auf zwei char x[2][3]-Arrays zeigen, die sich irgendwo im Speicher befinden können.
Hier ist eine Funktion, die einen Zeiger auf das Element in einem N-D-Array zurückgibt, auf das von einem n-dimensionalen Index gezeigt wird, wenn sowohl nicht-NULL-Strides als auch Suboffsets vorhanden sind
void *get_item_pointer(int ndim, void *buf, Py_ssize_t *strides,
Py_ssize_t *suboffsets, Py_ssize_t *indices) {
char *pointer = (char*)buf;
int i;
for (i = 0; i < ndim; i++) {
pointer += strides[i] * indices[i];
if (suboffsets[i] >=0 ) {
pointer = *((char**)pointer) + suboffsets[i];
}
}
return (void*)pointer;
}