Buffer-Protokoll

Bestimmte in Python verfügbare Objekte verpacken den 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 “. Bibliotheken von Drittanbietern können eigene Typen für spezielle Zwecke definieren, beispielsweise für die Bildverarbeitung oder die numerische Analyse.

Zwar hat jeder dieser Typen seine eigene Semantik, doch haben sie alle gemeinsam, dass sie auf einem möglicherweise großen Speicherpuffer basieren. In manchen Situationen ist es daher wünschenswert, direkt und ohne zwischengeschaltete Kopiervorgänge auf diesen Puffer zuzugreifen.

Python bietet eine solche Funktion auf C- und Python-Ebene in Form des Buffer-Protokolls. Dieses Protokoll hat zwei Seiten:

  • Auf der Produzentenseite kann ein Typ eine „Puffer-Schnittstelle“ exportieren, die es Objekten dieses Typs ermöglicht, Informationen über ihren zugrunde liegenden Puffer bereitzustellen. Diese Schnittstelle wird im Abschnitt „ Buffer Object Structures “ beschrieben; Informationen zu Python finden Sie unter Puffertypen emulieren.

  • Auf der Verbraucherseite stehen verschiedene Möglichkeiten zur Verfügung, um einen Zeiger auf die zugrunde liegenden Rohdaten eines Objekts (beispielsweise einen Methodenparameter) zu erhalten. Für Python siehe memoryview.

Einfache Objekte wie „ bytes “ und „ bytearray “ stellen ihren zugrunde liegenden Puffer in byteorientierter Form bereit. Andere Formen sind möglich; beispielsweise können die von einem „ array.array “ bereitgestellten Elemente Mehrbyte-Werte sein.

Ein Beispiel für die Nutzung der Puffer-Schnittstelle ist die Methode „ write() “ von Datei-Objekten: Jedes Objekt, das eine Reihe von Bytes über die Puffer-Schnittstelle exportieren kann, lässt sich in eine Datei schreiben. Während „ write() “ lediglich Lesezugriff auf den internen Inhalt des übergebenen Objekts benötigt, benötigen andere Methoden wie „ readinto() “ Schreibzugriff auf den Inhalt ihres Arguments. Die Puffer-Schnittstelle ermöglicht es Objekten, den Export von Lese-/Schreib- und Nur-Lese-Puffern selektiv zuzulassen oder abzulehnen.

Es gibt zwei Möglichkeiten für einen Nutzer der Puffer-Schnittstelle, einen Puffer über ein Zielobjekt zu beziehen:

In beiden Fällen muss „ PyBuffer_Release() “ aufgerufen werden, sobald der Puffer nicht mehr benötigt wird. Wird dies versäumt, kann dies zu verschiedenen Problemen wie beispielsweise Ressourcenlecks führen.

Added in version 3.12: Das Buffer-Protokoll ist nun in Python verfügbar, siehe Puffertypen emulieren und memoryview.

Pufferstruktur

Pufferstrukturen (oder einfach „Puffer“) sind nützlich, um dem Python-Programmierer die Binärdaten eines anderen Objekts zugänglich zu machen. Sie können auch als Zero-Copy-Slicing-Mechanismus verwendet werden. Dank ihrer Fähigkeit, auf einen Speicherblock zu verweisen, ist es möglich, dem Python-Programmierer beliebige Daten ganz einfach zugänglich zu machen. Bei dem Speicher könnte es sich um ein großes, konstantes Array in einer C-Erweiterung handeln, um einen rohen Speicherblock, der vor der Übergabe an eine Betriebssystembibliothek bearbeitet werden soll, oder um strukturierte Daten, die in ihrem nativen Speicherformat weitergegeben werden sollen.

Im Gegensatz zu den meisten vom Python-Interpreter bereitgestellten Datentypen sind Puffer keine Zeiger vom Typ „ PyObject “, sondern einfache C-Strukturen. Dadurch lassen sie sich sehr einfach erstellen und kopieren. Wenn ein generischer Wrapper um einen Puffer benötigt wird, kann ein memoryview- -Objekt erstellt werden.

Eine kurze Anleitung zum Erstellen eines Exportobjekts finden Sie unter Buffer Object Structures. Informationen zum Abrufen eines Puffers finden Sie unter 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 durch die Pufferfelder beschriebenen logischen Struktur. Dies kann eine beliebige Stelle innerhalb des zugrunde liegenden physischen Speicherblocks des Exporteurs sein. Bei negativen Werten von ` strides ` kann der Zeiger beispielsweise auf das Ende des Speicherblocks verweisen.

Bei zusammenhängenden Arrays verweist der Wert auf den Anfang des Speicherblocks.

PyObject *obj

Eine neue Referenz auf das exportierende Objekt. Die Referenz befindet sich im Besitz des Verbrauchers und wird automatisch freigegeben (d.h., die Referenzzählung wird verringert) und von PyBuffer_Release() auf ` NULL gesetzt. Das Feld entspricht dem Rückgabewert einer beliebigen Standard-C-API-Funktion.

Als Sonderfall gilt für temporäre Puffer, die von PyMemoryView_FromBuffer() oder PyBuffer_FillInfo() umschlossen sind, dass dieses Feld NULL lautet. Im Allgemeinen DÜRFEN exportierende Objekte dieses Schema NICHT verwenden.

Py_ssize_t len

product(shape) * itemsize. Bei zusammenhängenden Arrays entspricht dies der Länge des zugrunde liegenden Speicherblocks. Bei nicht zusammenhängenden Arrays entspricht dies der Länge, die die logische Struktur hätte, wenn sie in eine zusammenhängende Darstellung kopiert würde.

Der Zugriff auf ((char *)buf)[0] up to ((char *)buf)[len-1] ist nur zulässig, wenn der Puffer durch eine Anfrage abgerufen wurde, die zusammenhängende Daten garantiert. In den meisten Fällen handelt es sich bei einer solchen Anfrage um PyBUF_SIMPLE oder PyBUF_WRITABLE.

int readonly

Ein Indikator dafür, ob der Puffer schreibgeschützt ist. Dieses Feld wird durch das Flag „ PyBUF_WRITABLE “ gesteuert.

Py_ssize_t itemsize

Größe eines einzelnen Elements in Byte. Entspricht dem Wert von „ struct.calcsize() “, der für Werte aufgerufen wird, die nicht „NULL “ format sind.

Wichtige Ausnahme: Wenn ein Verbraucher einen Puffer ohne das Flag „ PyBUF_FORMAT “ anfordert, wird „ format “ auf „ NULL “ gesetzt, „ itemsize “ behält jedoch weiterhin den Wert des ursprünglichen Formats bei.

Wenn „ shape “ vorhanden ist, gilt weiterhin die Gleichung „ product(shape) * itemsize == len “, und der Verbraucher kann „ itemsize “ verwenden, um durch den Puffer zu navigieren.

Wenn „ shape “ infolge einer Anfrage an „ PyBUF_SIMPLE “ oder „ PyBUF_WRITABLE “ zu „ NULL “ führt, muss der Verbraucher „ itemsize “ ignorieren und von „ itemsize == 1 “ ausgehen.

char *format

Eine mit NULL abgeschlossene Zeichenkette im Syntaxstil des Moduls „ struct “, die den Inhalt eines einzelnen Elements beschreibt. Handelt es sich dabei um „ NULL “, wird „ "B" “ (vorzeichenlose Bytes) angenommen.

Dieses Feld wird durch das Flag „ PyBUF_FORMAT “ gesteuert.

int ndim

Die Anzahl der Dimensionen, die der Speicher als n-dimensionales Array darstellt. Wenn diese Zahl 0 ist, verweist buf auf ein einzelnes Element, das einen Skalar darstellt. In diesem Fall MÜSSEN shape, strides und suboffsets NULL sein. Die maximale Anzahl der Dimensionen ist durch PyBUF_MAX_NDIM gegeben.

Py_ssize_t *shape

Ein Array von Typ „ Py_ssize_t “ der Länge „ ndim “, das die Form des Speichers als n-dimensionales Array angibt. Beachten Sie, dass „ shape[0] * ... * shape[ndim-1] * itemsize “ unbedingt gleich „ len “ sein MUSS.

Die Werte für „Shape“ sind auf „ shape[n] >= 0 “ beschränkt. Der Fall „ shape[n] == 0 “ erfordert besondere Beachtung. Weitere Informationen finden Sie unter „complex arrays“.

Das Shape-Array ist für den Verbraucher schreibgeschützt.

Py_ssize_t *strides

Ein Array mit dem Namen „ Py_ssize_t “ der Länge „ ndim “, das die Anzahl der Bytes angibt, die übersprungen werden müssen, um in jeder Dimension zu einem neuen Element zu gelangen.

Stride-Werte können beliebige ganze Zahlen sein. Bei regulären Arrays sind Strides in der Regel positiv, aber ein Verbraucher MUSS in der Lage sein, den Fall strides[n] <= 0 zu verarbeiten. Weitere Informationen finden Sie unter komplexe Arrays.

Das „strides“-Array ist für den Verbraucher schreibgeschützt.

Py_ssize_t *suboffsets

Ein Array von Py_ssize_t der Länge ndim. Wenn suboffsets[n] >= 0, sind die in der n-ten Dimension gespeicherten Werte Zeiger, und der Suboffset-Wert gibt an, wie viele Bytes nach der Dereferenzierung zu jedem Zeiger addiert werden sollen. Ein negativer Suboffset-Wert bedeutet, dass keine Dereferenzierung erfolgen soll (Striding in einem zusammenhängenden Speicherblock).

Sind alle Teilversätze negativ (d. h., es ist keine Dereferenzierung erforderlich), muss dieses Feld den Wert „ NULL “ annehmen (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 complex arrays.

Das „suboffsets“-Array ist für den Verbraucher schreibgeschützt.

void *internal

Dieser Wert ist für die interne Verwendung durch das exportierende Objekt bestimmt. Er kann beispielsweise vom Exporter in einen Ganzzahlwert umgewandelt und dazu verwendet werden, Flags zu speichern, die angeben, ob die Arrays „shape“, „strides“ und „suboffsets“ freigegeben werden müssen, wenn der Puffer freigegeben wird. Der Verbraucher DARF diesen Wert NICHT ändern.

Konstanten:

PyBUF_MAX_NDIM
Teil der Stable ABI seit Version 3.11.

Die maximale Anzahl an Dimensionen, die der Speicher darstellt. Exporteure MÜSSEN diese Grenze einhalten; Nutzer mehrdimensionaler Puffer SOLLTEN in der Lage sein, bis zu PyBUF_MAX_NDIM Dimensionen zu verarbeiten. Derzeit auf 64 festgelegt.

Arten von Pufferanforderungen

Puffer werden in der Regel dadurch abgerufen, dass eine Pufferanforderung über PyObject_GetBuffer() an ein exportierendes Objekt gesendet wird. Da die Komplexität der logischen Struktur des Speichers stark variieren kann, gibt der Verbraucher über das Argument flags den genauen Puffertyp an, den er verarbeiten kann.

Alle „ Py_buffer “-Felder werden durch den Anfragetyp eindeutig definiert.

anfrageunabhängige Felder

Die folgenden Felder werden von Flags nicht beeinflusst und müssen stets mit den richtigen Werten ausgefüllt werden: obj, buf, len, itemsize, ndim.

schreibgeschützt, Format

PyBUF_WRITABLE
Teil der Stable ABI seit Version 3.11.

Steuert das Feld „ readonly “. Ist dieses gesetzt, MUSS der Exporter einen beschreibbaren Puffer bereitstellen oder andernfalls einen Fehler melden. Andernfalls DARF der Exporter entweder einen schreibgeschützten oder einen beschreibbaren Puffer bereitstellen, die Wahl MUSS jedoch für alle Verbraucher einheitlich sein. Beispielsweise kann PyBUF_SIMPLE | PyBUF_WRITABLE verwendet werden, um einen einfachen beschreibbaren Puffer anzufordern.

PyBUF_WRITEABLE

This is a soft deprecated alias to PyBUF_WRITABLE.

PyBUF_FORMAT
Teil der Stable ABI seit Version 3.11.

Steuert das Feld „ format “. Wenn diese Option aktiviert ist, MUSS dieses Feld korrekt ausgefüllt werden. Andernfalls MUSS dieses Feld auf „ NULL “ gesetzt sein.

PyBUF_WRITABLE kann mit | an jedes der Flags im nächsten Abschnitt angehängt werden. Da „ PyBUF_SIMPLE “ auf 0 gesetzt ist, kann „ PyBUF_WRITABLE “ als eigenständiges Flag verwendet werden, um einen einfachen beschreibbaren Puffer anzufordern.

PyBUF_FORMAT muss mit einem der Flags außer „ PyBUF_SIMPLE “ kombiniert werden, da letzteres bereits das Format „ B “ (vorzeichenlose Bytes) impliziert. „ PyBUF_FORMAT “ kann nicht eigenständig verwendet werden.

Form, Schrittlängen, Teilversätze

Die Flags, die die logische Struktur des Speichers steuern, sind in absteigender Reihenfolge ihrer Komplexität aufgeführt. Beachten Sie, dass jedes Flag alle Bits der darunter liegenden Flags enthält.

Anfrage

Form

Schritte

Teilversätze

PyBUF_INDIRECT
Teil der Stable ABI seit Version 3.11.

ja

ja

falls nötig

PyBUF_STRIDES
Teil der Stable ABI seit Version 3.11.

ja

ja

NULL

PyBUF_ND
Teil der Stable ABI seit Version 3.11.

ja

NULL

NULL

PyBUF_SIMPLE
Teil der Stable ABI seit Version 3.11.

NULL

NULL

NULL

Anträge auf zusammenhängende Grundstücke

Ein Zusammenhang nach C- oder Fortran-Ordnung kann ausdrücklich angefordert werden, mit oder ohne Stride-Angabe. Ohne Stride-Angabe muss der Puffer C-zusammenhängend sein.

Anfrage

Form

Schritte

Teilversätze

Contig

PyBUF_C_CONTIGUOUS
Teil der Stable ABI seit Version 3.11.

ja

ja

NULL

C

PyBUF_F_CONTIGUOUS
Teil der Stable ABI seit Version 3.11.

ja

ja

NULL

F

PyBUF_ANY_CONTIGUOUS
Teil der Stable ABI seit Version 3.11.

ja

ja

NULL

C oder F

PyBUF_ND

ja

NULL

NULL

C

zusammengesetzte Anfragen

Alle möglichen Anfragen werden vollständig durch eine Kombination der im vorigen Abschnitt genannten Flags definiert. Der Einfachheit halber stellt das Pufferprotokoll häufig verwendete Kombinationen als einzelne Flags zur Verfügung.

In der folgenden Tabelle steht U für eine undefinierte Kontiguität. Der Verbraucher müsste die Funktion „ PyBuffer_IsContiguous() “ aufrufen, um die Kontiguität zu ermitteln.

Anfrage

Form

Schritte

Teilversätze

Contig

schreibgeschützt

Format

PyBUF_FULL
Teil der Stable ABI seit Version 3.11.

ja

ja

falls nötig

U

0

ja

PyBUF_FULL_RO
Teil der Stable ABI seit Version 3.11.

ja

ja

falls nötig

U

1 oder 0

ja

PyBUF_RECORDS
Teil der Stable ABI seit Version 3.11.

ja

ja

NULL

U

0

ja

PyBUF_RECORDS_RO
Teil der Stable ABI seit Version 3.11.

ja

ja

NULL

U

1 oder 0

ja

PyBUF_STRIDED
Teil der Stable ABI seit Version 3.11.

ja

ja

NULL

U

0

NULL

PyBUF_STRIDED_RO
Teil der Stable ABI seit Version 3.11.

ja

ja

NULL

U

1 oder 0

NULL

PyBUF_CONTIG
Teil der Stable ABI seit Version 3.11.

ja

NULL

NULL

C

0

NULL

PyBUF_CONTIG_RO
Teil der Stable ABI seit Version 3.11.

ja

NULL

NULL

C

1 oder 0

NULL

Komplexe Arrays

NumPy-Stil: Form und Schritte

Die logische Struktur von Arrays im NumPy-Stil wird durch itemsize, ndim, shape und strides definiert.

Wenn ndim == 0 gilt, wird die Speicheradresse, auf die buf verweist, als Skalar der Größe itemsize interpretiert. In diesem Fall sind sowohl shape als auch strides NULL .

Wenn „ strides “ auf „ NULL “ gesetzt ist, wird das Array als standardmäßiges n-dimensionales 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 bereits erwähnt, kann ` buf ` auf eine beliebige Stelle innerhalb des eigentlichen Speicherblocks verweisen. Ein Exporter kann die Gültigkeit eines Puffers mit dieser Funktion überprüfen:

def verify_structure(memlen, itemsize, ndim, shape, strides, offset):
    """Überprüft, ob die Parameter ein gültiges Array innerhalb
       der Grenzen des zugewiesenen Speichers darstellen:
           char *mem: Anfang des physischen Speicherblocks
           memlen: Länge des physischen Speicherblocks
           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

    wenn ndim <= 0:
        gibt ndim == 0 und nicht shape und nicht strides zurück
    wenn 0 in shape enthalten ist:
        gibt True zurück

    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: Form, Schritte und Teilversätze

Zusätzlich zu den regulären Elementen können Arrays im PIL-Stil Zeiger enthalten, denen man folgen muss, um zum nächsten Element einer Dimension zu gelangen. Beispielsweise lässt sich das reguläre dreidimensionale C-Array char v[2][2][3] auch als Array aus 2 Zeigern auf 2 zweidimensionale Arrays betrachten: char (*v[2])[2][3]. In der Suboffset-Darstellung können diese beiden Zeiger am Anfang von buf eingebettet werden und auf zwei Arrays vom Typ char x[2][3] verweisen, die sich an beliebiger Stelle im Speicher befinden können.

Hier ist eine Funktion, die einen Zeiger auf das Element in einem N-dimensionalen Array zurückgibt, auf das ein N-dimensionaler Index verweist, wenn sowohl nicht-NULL e Schritte als auch Teilversätze 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;
}