PEP 3118 – Überarbeitung des Pufferprotokolls
- Autor:
- Travis Oliphant <oliphant at ee.byu.edu>, Carl Banks <pythondev at aerojockey.com>
- Status:
- Final
- Typ:
- Standards Track
- Erstellt:
- 28-Aug-2006
- Python-Version:
- 3.0
- Post-History:
- 09-Apr-2007
Zusammenfassung
Diese PEP schlägt eine Neugestaltung der Puffer-Schnittstelle (PyBufferProcs Funktionszeiger) vor, um die Art und Weise zu verbessern, wie Python den Speicher in Python 3.0 teilt.
Insbesondere wird vorgeschlagen, den Zeichenpufferteil der API zu eliminieren und den Teil mit mehreren Segmenten neu zu gestalten, während gleichzeitig das Teilen von gestrecktem Speicher ermöglicht wird. Darüber hinaus wird die neue Puffer-Schnittstelle das Teilen jeder mehrdimensionalen Natur des Speichers und des Datenformats, das der Speicher enthält, ermöglichen.
Diese Schnittstelle wird es jedem Erweiterungsmodul ermöglichen, entweder Objekte zu erstellen, die Speicher teilen, oder Algorithmen zu erstellen, die Rohdaten aus beliebigen Objekten verwenden und manipulieren, die die Schnittstelle exportieren.
Begründung
Das Pufferprotokoll von Python 2.X ermöglicht den Austausch eines Zeigers auf eine Sequenz interner Puffer zwischen verschiedenen Python-Typen. Diese Funktionalität ist *extrem* nützlich für das Teilen großer Speichersegmente zwischen verschiedenen High-Level-Objekten, aber sie ist zu begrenzt und weist Probleme auf.
- Es gibt die wenig genutzte Option „Sequenz von Segmenten“ (bf_getsegcount), die nicht gut motiviert ist.
- Es gibt die anscheinend redundante Zeichenpuffer-Option (bf_getcharbuffer).
- Es gibt keine Möglichkeit für einen Konsumenten, dem Puffer-API-exportierenden Objekt mitzuteilen, dass er mit seiner Ansicht des Speichers „fertig“ ist, und somit keine Möglichkeit für das exportierende Objekt sicherzustellen, dass es sicher ist, den Zeiger auf den Speicher, den es besitzt, neu zuzuweisen (z. B. das Array-Objekt, das seinen Speicher nach dem Teilen mit dem Pufferobjekt, das den ursprünglichen Zeiger hielt, neu zuweist, führte zum berüchtigten Puffer-Objekt-Problem).
- Speicher ist nur ein Zeiger mit einer Länge. Es gibt keine Möglichkeit zu beschreiben, was sich im Speicher befindet (Gleitkommazahl, Ganzzahl, C-Struktur usw.).
- Es gibt keine Shape-Informationen für den Speicher. Mehrere Array-ähnliche Python-Typen könnten jedoch von einer standardisierten Methode zur Beschreibung der Shape-Interpretation des Speichers profitieren (wxPython, GTK, pyQT, CVXOPT, PyVox, Audio- und Videobibliotheken, ctypes, NumPy, Datenbank-Schnittstellen usw.).
- Es gibt keine Möglichkeit, diskontinuierlichen Speicher zu teilen (außer über die Vorstellung einer Segmentsequenz).
Es gibt zwei weit verbreitete Bibliotheken, die das Konzept des diskontinuierlichen Speichers verwenden: PIL und NumPy. Ihre Sichtweise auf diskontinuierliche Arrays ist jedoch unterschiedlich. Das vorgeschlagene Pufferprotokoll ermöglicht das Teilen beider Speicher-Modelle. Exporteure verwenden typischerweise nur einen Ansatz, und Konsumenten können wählen, ob sie diskontinuierliche Arrays jedes Typs unterstützen, wie sie es wünschen.
NumPy verwendet die Vorstellung von konstantem Stride in jeder Dimension als Grundkonzept eines Arrays. Mit diesem Konzept kann eine einfache Unterregion eines größeren Arrays beschrieben werden, ohne die Daten zu kopieren. Daher sind die Strides-Informationen die zusätzlichen Informationen, die geteilt werden müssen.
PIL verwendet eine undurchsichtigere Speicherrepräsentation. Manchmal ist ein Bild in einem zusammenhängenden Speichersegment enthalten, manchmal ist es in einem Array von Zeigern auf die zusammenhängenden Segmente (normalerweise Zeilen) des Bildes enthalten. PIL ist der Ursprung der Idee von mehreren Puffersegmenten im ursprünglichen Pufferprotokoll.
Das gestreckte Speichermodell von NumPy wird häufiger in numerischen Bibliotheken verwendet, und da es so einfach ist, macht es Sinn, die Speicherfreigabe mit diesem Modell zu unterstützen. Das Speichermodell von PIL wird manchmal in C-Code verwendet, wo ein 2D-Array dann über doppelte Zeigerindirektion zugegriffen werden kann: z. B.
image[i][j].Das Pufferprotokoll sollte es dem Objekt ermöglichen, eines dieser Speichermodelle zu exportieren. Konsumenten können entweder zusammenhängenden Speicher verlangen oder Code schreiben, um eines oder beide dieser Speichermodelle zu verarbeiten.
Vorschlagsübersicht
- Eliminieren Sie die Zeichenpuffer- und Mehrsegment-Abschnitte des Pufferprotokolls.
- Vereinheitlichen Sie die Lese- und Schreibversionen des Pufferabrufs.
- Fügen Sie eine neue Funktion zur Schnittstelle hinzu, die aufgerufen werden soll, wenn der Konsument des Objekts mit dem Speicherbereich „fertig“ ist.
- Fügen Sie eine neue Variable hinzu, die es der Schnittstelle ermöglicht zu beschreiben, was sich im Speicher befindet (vereinheitlicht, was derzeit in struct und array geschieht).
- Fügen Sie eine neue Variable hinzu, die es dem Protokoll ermöglicht, Shape-Informationen zu teilen.
- Fügen Sie eine neue Variable zum Teilen von Strides-Informationen hinzu.
- Fügen Sie einen neuen Mechanismus zum Teilen von Arrays hinzu, die über Zeigerindirektion zugegriffen werden müssen.
- Korrigieren Sie alle Objekte im Kern und in der Standardbibliothek, damit sie dem neuen Protokoll entsprechen.
- Erweitern Sie das struct-Modul, um mehr Format-Spezifizierer zu handhaben.
- Erweitern Sie das Pufferobjekt zu einem neuen Speicherobjekt, das eine Python-Hülle um das Pufferprotokoll legt.
- Fügen Sie einige Funktionen hinzu, um das Kopieren von zusammenhängenden Daten in und aus Objekten, die das Pufferprotokoll unterstützen, zu erleichtern.
Spezifikation
Während die neue Spezifikation die gemeinsame Nutzung komplizierter Speicher ermöglicht, können weiterhin einfache zusammenhängende Byte-Puffer von einem Objekt abgerufen werden. Tatsächlich ermöglicht das neue Protokoll einen standardmäßigen Mechanismus dafür, selbst wenn das ursprüngliche Objekt nicht als zusammenhängender Speicherblock dargestellt ist.
Der einfachste Weg, einen einfachen zusammenhängenden Speicherblock zu erhalten, ist die Verwendung der bereitgestellten C-API, um einen Speicherblock zu erhalten.
Ändern Sie die Struktur PyBufferProcs zu
typedef struct {
getbufferproc bf_getbuffer;
releasebufferproc bf_releasebuffer;
} PyBufferProcs;
Beide dieser Routinen sind für ein Typobjekt optional.
typedef int (*getbufferproc)(PyObject *obj, PyBuffer *view, int flags)
Diese Funktion gibt bei Erfolg 0 und bei Fehler -1 zurück (und löst einen Fehler aus). Die erste Variable ist das „exportierende“ Objekt. Das zweite Argument ist die Adresse einer bufferinfo-Struktur. Beide Argumente dürfen niemals NULL sein.
Das dritte Argument gibt an, mit welcher Art von Puffer der Konsument umgehen kann und daher, welche Art von Puffer der Exporteur zurückgeben darf. Die neue Puffer-Schnittstelle ermöglicht weitaus komplexere Speicherteilungs-Möglichkeiten. Einige Konsumenten können möglicherweise nicht mit der gesamten Komplexität umgehen, möchten aber möglicherweise sehen, ob der Exporteur ihnen eine einfachere Ansicht seines Speichers gewährt.
Zusätzlich können einige Exporteure Speicher nicht auf jede mögliche Weise teilen und müssen Fehler auslösen, um einigen Konsumenten zu signalisieren, dass etwas einfach nicht möglich ist. Diese Fehler sollten PyErr_BufferError sein, es sei denn, es liegt ein anderer Fehler vor, der tatsächlich das Problem verursacht. Der Exporteur kann die Flags-Informationen verwenden, um zu vereinfachen, wie viel von der PyBuffer-Struktur mit nicht standardmäßigen Werten gefüllt wird und/oder einen Fehler auszulösen, wenn das Objekt keine einfachere Ansicht seines Speichers unterstützen kann.
Der Exporteur sollte immer alle Elemente der Pufferstruktur füllen (mit Standardwerten oder NULLs, wenn nichts anderes angefordert wird). Die Funktion PyBuffer_FillInfo kann für einfache Fälle verwendet werden.
Zugriffsflags
Einige Flags sind nützlich, um eine bestimmte Art von Speichersegment anzufordern, während andere dem Exporteur anzeigen, welche Art von Informationen der Konsument verarbeiten kann. Wenn bestimmte Informationen nicht vom Konsumenten angefordert werden, der Exporteur jedoch seinen Speicher nicht ohne diese Informationen teilen kann, sollte ein PyErr_BufferError ausgelöst werden.
PyBUF_SIMPLE
Dies ist der Standardzustand der Flags (0). Der zurückgegebene Puffer kann schreibbaren Speicher enthalten oder auch nicht. Das Format wird als unsignierte Bytes angenommen. Dies ist eine eigenständige Flagge. Sie muss niemals mit anderen verknüpft (|) werden. Der Exporteur löst einen Fehler aus, wenn er keinen solchen zusammenhängenden Byte-Puffer bereitstellen kann.
PyBUF_WRITABLE
Der zurückgegebene Puffer muss schreibbar sein. Wenn er nicht schreibbar ist, löst dies einen Fehler aus.
PyBUF_FORMAT
Der zurückgegebene Puffer muss echte Formatinformationen enthalten, wenn dieses Flag gesetzt ist. Dies würde verwendet werden, wenn der Konsument überprüfen möchte, welche Art von Daten tatsächlich gespeichert ist. Ein Exporteur sollte diese Informationen immer bereitstellen können, wenn sie angefordert werden. Wenn das Format nicht explizit angefordert wird, muss das Format alsNULLzurückgegeben werden (was „B“, oder unsignierte Bytes bedeutet).
PyBUF_ND
Der zurückgegebene Puffer muss Shape-Informationen bereitstellen. Der Speicher wird als C-Stil zusammenhängend angenommen (die letzte Dimension variiert am schnellsten). Der Exporteur kann einen Fehler auslösen, wenn er keine Art von zusammenhängendem Puffer bereitstellen kann. Wenn dies nicht angegeben ist, ist shape NULL.
PyBUF_STRIDES (impliziert PyBUF_ND)
Der zurückgegebene Puffer muss Strides-Informationen bereitstellen (d. h. die Strides dürfen nicht NULL sein). Dies würde verwendet werden, wenn der Konsument gestreckte, diskontinuierliche Arrays verarbeiten kann. Die Verarbeitung von Strides setzt automatisch voraus, dass man Shape verarbeiten kann. Der Exporteur kann einen Fehler auslösen, wenn er keine reine gestreckte Darstellung der Daten bereitstellen kann (d. h. ohne die Suboffsets).
PyBUF_C_CONTIGUOUSPyBUF_F_CONTIGUOUSPyBUF_ANY_CONTIGUOUSDiese Flags geben an, dass der zurückgegebene Puffer jeweils C-zusammenhängend (die letzte Dimension variiert am schnellsten), Fortran-zusammenhängend (die erste Dimension variiert am schnellsten) oder eines von beiden sein muss. Alle diese Flags implizieren PyBUF_STRIDES und garantieren, dass die Strides-Buffer-Informationsstruktur korrekt gefüllt wird.
PyBUF_INDIRECT (impliziert PyBUF_STRIDES)
Der zurückgegebene Puffer muss Suboffset-Informationen enthalten (die NULL sein können, wenn keine Suboffsets benötigt werden). Dies würde verwendet werden, wenn der Konsument indirekte Array-Referenzen verarbeiten kann, die durch diese Suboffsets impliziert sind.
Spezialisierte Kombinationen von Flags für bestimmte Arten der Speicherfreigabe.
Mehrdimensional (aber zusammenhängend)PyBUF_CONTIG(PyBUF_ND | PyBUF_WRITABLE)PyBUF_CONTIG_RO(PyBUF_ND)Mehrdimensional mit Strides, aber ausgerichtet.
PyBUF_STRIDED(PyBUF_STRIDES | PyBUF_WRITABLE)PyBUF_STRIDED_RO(PyBUF_STRIDES)Mehrdimensional mit Strides und nicht notwendigerweise ausgerichtet.
PyBUF_RECORDS(PyBUF_STRIDES | PyBUF_WRITABLE | PyBUF_FORMAT)PyBUF_RECORDS_RO(PyBUF_STRIDES | PyBUF_FORMAT)Mehrdimensional mit Suboffsets.
PyBUF_FULL(PyBUF_INDIRECT | PyBUF_WRITABLE | PyBUF_FORMAT)PyBUF_FULL_RO(PyBUF_INDIRECT | PyBUF_FORMAT)
Somit würde der Konsument, der einfach nur einen zusammenhängenden Byte-Block vom Objekt wünscht, PyBUF_SIMPLE verwenden, während ein Konsument, der versteht, wie man die kompliziertesten Fälle nutzt, PyBUF_FULL verwenden könnte.
Die Formatinformationen sind nur garantiert nicht-NULL, wenn PyBUF_FORMAT im Flag-Argument enthalten ist, andernfalls wird erwartet, dass der Konsument unsignierte Bytes annimmt.
Es gibt eine C-API, die einfache exportierende Objekte verwenden können, um die Pufferinformationsstruktur korrekt gemäß den bereitgestellten Flags zu füllen, wenn ein zusammenhängender Block von „unsignierten Bytes“ alles ist, was exportiert werden kann.
Die Py_buffer Struktur
Die Pufferinformationsstruktur ist
struct bufferinfo {
void *buf;
Py_ssize_t len;
int readonly;
const char *format;
int ndim;
Py_ssize_t *shape;
Py_ssize_t *strides;
Py_ssize_t *suboffsets;
Py_ssize_t itemsize;
void *internal;
} Py_buffer;
Vor dem Aufruf der bf_getbuffer-Funktion kann die bufferinfo-Struktur mit allem gefüllt werden, aber das buf-Feld muss NULL sein, wenn ein neuer Puffer angefordert wird. Nach der Rückkehr von bf_getbuffer ist die bufferinfo-Struktur mit relevanten Informationen über den Puffer gefüllt. Dieselbe bufferinfo-Struktur muss an bf_releasebuffer (falls vorhanden) übergeben werden, wenn der Konsument mit dem Speicher fertig ist. Der Aufrufer ist dafür verantwortlich, eine Referenz auf obj zu halten, bis releasebuffer aufgerufen wird (d. h. der Aufruf von bf_getbuffer ändert die Referenzanzahl von obj nicht).
Die Member der bufferinfo-Struktur sind
buf- ein Zeiger auf den Anfang des Speichers für das Objekt
len- die gesamten Bytes an Speicher, die das Objekt verwendet. Dies sollte gleich dem Produkt des Shape-Arrays multipliziert mit der Anzahl der Bytes pro Element des Speichers sein.
readonly- eine Ganzzahlvariable, die angibt, ob der Speicher schreibgeschützt ist oder nicht. 1 bedeutet, dass der Speicher schreibgeschützt ist, null bedeutet, dass der Speicher schreibbar ist.
format- eine NULL-terminierte Formatzeichenkette (gemäß der struct-Stil-Syntax einschließlich Erweiterungen), die angibt, was sich in jedem Element des Speichers befindet. Die Anzahl der Elemente ist len / itemsize, wobei itemsize die Anzahl der Bytes ist, die durch das Format impliziert werden. Dies kann NULL sein, was standardmäßige unsignierte Bytes („B“) impliziert.
ndim- eine Variable, die die Anzahl der Dimensionen speichert, die der Speicher darstellt. Muss >=0 sein. Ein Wert von 0 bedeutet, dass shape, strides und suboffsets
NULLsein müssen (d. h. der Speicher stellt einen Skalar dar). shape- ein Array von
Py_ssize_tder Längendims, das die Form des Speichers als N-D-Array angibt. Beachten Sie, dass((*shape)[0] * ... * (*shape)[ndims-1])*itemsize = len. Wenn ndims 0 ist (was einen Skalar bedeutet), dann muss diesNULLsein. strides- Adresse einer
Py_ssize_t*-Variable, die mit einem Zeiger auf ein Array vonPy_ssize_tder Längendimsgefüllt wird (oderNULL, wennndims0 ist). Das gibt die Anzahl der Bytes an, die übersprungen werden müssen, um zum nächsten Element in jeder Dimension zu gelangen. Wenn dies vom Aufrufer nicht angefordert wird (PyBUF_STRIDESist nicht gesetzt), dann sollte dies auf NULL gesetzt werden, was ein C-Stil zusammenhängendes Array anzeigt, oder PyExc_BufferError auslösen, wenn dies nicht möglich ist. suboffsets- Adresse einer
Py_ssize_t *-Variable, die mit einem Zeiger auf ein Array vonPy_ssize_tder Länge*ndimsgefüllt wird. Wenn diese Suboffset-Zahlen >=0 sind, dann ist der Wert, der entlang der angegebenen Dimension gespeichert ist, ein Zeiger, und der Suboffset-Wert gibt an, wie viele Bytes zum Zeiger hinzugefügt werden müssen, nachdem dereferenziert wurde. Ein negativer Suboffset-Wert bedeutet, dass keine Dereferenzierung stattfinden soll (Striding in einem zusammenhängenden Speicherblock). Wenn alle Suboffsets negativ sind (d. h. keine Dereferenzierung erforderlich ist, dann muss dies NULL sein (der Standardwert). Wenn dies vom Aufrufer nicht angefordert wird (PyBUF_INDIRECT ist nicht gesetzt), dann sollte dies auf NULL gesetzt werden oder ein PyExc_BufferError ausgelöst werden, wenn dies nicht möglich ist.Zur Verdeutlichung ist hier eine Funktion, die einen Zeiger auf das Element in einem N-D-Array zurückgibt, das von einem N-dimensionalen Index adressiert 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; }
Beachten Sie, dass der Suboffset „nach“ der Dereferenzierung hinzugefügt wird. Somit würde das Slicing in der i-ten Dimension zu den Suboffsets in der (i-1)-ten Dimension hinzugefügt werden. Das Slicing in der ersten Dimension würde den Speicherort des Startzeigers direkt ändern (d. h. buf würde modifiziert werden).
itemsize- Dies ist ein Speicher für die Elementgröße (in Bytes) jedes Elements des freigegebenen Speichers. Er ist technisch nicht notwendig, da er mit
PyBuffer_SizeFromFormaterhalten werden kann. Ein Exporteur kennt diese Informationen jedoch möglicherweise, ohne die Formatzeichenfolge zu parsen, und es ist notwendig, die Elementgröße für die korrekte Interpretation des Striding zu kennen. Daher ist die Speicherung bequemer und schneller. internal- Dies ist für die interne Verwendung durch das exportierende Objekt. Zum Beispiel könnte dies von dem Exporteur in eine Ganzzahl umgewandelt und verwendet werden, um Flags zu speichern, ob die Arrays shape, strides und suboffsets beim Freigeben des Puffers freigegeben werden müssen. Der Konsument sollte diesen Wert niemals ändern.
Der Exporteur ist dafür verantwortlich, sicherzustellen, dass jeder Speicher, auf den über buf, format, shape, strides und suboffsets gezeigt wird, bis zum Aufruf von releasebuffer gültig ist. Wenn der Exporteur die Form, Strides und/oder Suboffsets eines Objekts ändern möchte, bevor releasebuffer aufgerufen wird, sollte er diese Arrays beim Aufruf von getbuffer zuweisen (auf sie in der bereitgestellten buffer-info-Struktur zeigen) und sie beim Aufruf von releasebuffer freigeben.
Freigabe des Puffers
Die gleiche bufferinfo-Struktur sollte im release-buffer-Schnittstellenaufruf verwendet werden. Der Aufrufer ist für den Speicher der Py_buffer-Struktur selbst verantwortlich.
typedef void (*releasebufferproc)(PyObject *obj, Py_buffer *view)
Aufrufer von getbufferproc müssen sicherstellen, dass diese Funktion aufgerufen wird, wenn zuvor vom Objekt erworbener Speicher nicht mehr benötigt wird. Der Exporteur der Schnittstelle muss sicherstellen, dass jeder Speicher, auf den in der bufferinfo-Struktur gezeigt wird, bis zum Aufruf von releasebuffer gültig bleibt.
Wenn die Funktion bf_releasebuffer nicht bereitgestellt wird (d. h. sie ist NULL), muss sie niemals aufgerufen werden.
Exporteure müssen eine bf_releasebuffer-Funktion definieren, wenn sie ihre Speicher-, Strides-, Shape-, Suboffsets- oder Formatvariablen neu zuweisen können, die sie möglicherweise über die struct bufferinfo teilen. Mehrere Mechanismen könnten verwendet werden, um zu verfolgen, wie viele getbuffer-Aufrufe gemacht und geteilt wurden. Entweder könnte eine einzelne Variable verwendet werden, um die Anzahl der exportierten „Ansichten“ zu verfolgen, oder eine verkettete Liste von gefüllten bufferinfo-Strukturen könnte in jedem Objekt geführt werden.
Alles, was speziell vom Exporteur verlangt wird, ist jedoch sicherzustellen, dass jeder Speicher, der über die bufferinfo-Struktur geteilt wird, bis releasebuffer für die Pufferstruktur aufgerufen wird, die diesen Speicher exportiert, gültig bleibt.
Neue C-API-Aufrufe werden vorgeschlagen
int PyObject_CheckBuffer(PyObject *obj)
Gibt 1 zurück, wenn die getbuffer-Funktion verfügbar ist, andernfalls 0.
int PyObject_GetBuffer(PyObject *obj, Py_buffer *view,
int flags)
Dies ist eine C-API-Version des getbuffer-Funktionsaufrufs. Sie prüft, ob das Objekt den erforderlichen Funktionszeiger hat und löst den Aufruf aus. Gibt -1 zurück und löst bei einem Fehler einen Fehler aus, gibt bei Erfolg 0 zurück.
void PyBuffer_Release(PyObject *obj, Py_buffer *view)
Dies ist eine C-API-Version des releasebuffer-Funktionsaufrufs. Sie prüft, ob das Objekt den erforderlichen Funktionszeiger hat und löst den Aufruf aus. Diese Funktion ist immer erfolgreich, auch wenn für das Objekt keine releasebuffer-Funktion vorhanden ist.
PyObject *PyObject_GetMemoryView(PyObject *obj)
Gibt ein memory-view-Objekt von einem Objekt zurück, das die Puffer-Schnittstelle definiert.
Ein memory-view-Objekt ist ein erweitertes Pufferobjekt, das das Pufferobjekt ersetzen könnte (muss es aber nicht, da dieses als einfaches 1-D-Memory-View-Objekt beibehalten werden kann). Seine C-Struktur ist
typedef struct {
PyObject_HEAD
PyObject *base;
Py_buffer view;
} PyMemoryViewObject;
Dies ist funktional ähnlich dem aktuellen Pufferobjekt, außer dass eine Referenz auf die Basis gehalten wird und die Speicheransicht nicht neu erfasst wird. Somit hält dieses memory-view-Objekt den Speicher der Basis, bis es gelöscht wird.
Dieses memory-view-Objekt unterstützt mehrdimensionales Slicing und wird das erste Objekt sein, das in Python dafür bereitgestellt wird. Slices des memory-view-Objekts sind andere memory-view-Objekte mit derselben Basis, aber mit einer anderen Ansicht des Basisobjekts.
Wenn ein „Element“ aus dem memory-view zurückgegeben wird, ist es immer ein Bytes-Objekt, dessen Format durch das Formatattribut des Memoryview-Objekts interpretiert werden sollte. Das struct-Modul kann verwendet werden, um die Bytes in Python zu „dekodieren“, falls gewünscht. Oder der Inhalt kann an ein NumPy-Array oder ein anderes Objekt übergeben werden, das das Pufferprotokoll nutzt.
Der Python-Name wird sein
__builtin__.memoryview
Methoden
__getitem__ (unterstützt mehrdimensionales Slicing)__setitem__ (unterstützt mehrdimensionales Slicing)tobytes (erhält ein neues Bytes-Objekt einer Kopie des Speichers).tolist (erhält eine „verschachtelte“ Liste des Speichers. Alles wird in Standard-Python-Objekte interpretiert, so wie es struct.unpack tun würde – tatsächlich verwendet es struct.unpack, um dies zu erreichen).Attribute (entnommen aus dem Speicher des Basisobjekts)
formatitemsizeshapestridessuboffsetsreadonlyndim
Py_ssize_t PyBuffer_SizeFromFormat(const char *)
Gibt die implizierte Elementgröße des Datenformatbereichs aus einer struct-ähnlichen Beschreibung zurück.
PyObject * PyMemoryView_GetContiguous(PyObject *obj, int buffertype,
char fortran)
Gibt ein memoryview-Objekt für einen zusammenhängenden Speicherblock zurück, der von obj dargestellt wird. Wenn eine Kopie erstellt werden muss (weil der von obj referenzierte Speicher nicht zusammenhängend ist), wird ein neues Bytes-Objekt erstellt und wird zum Basisobjekt für das zurückgegebene Memoryview-Objekt.
Das Argument buffertype kann PyBUF_READ, PyBUF_WRITE, PyBUF_UPDATEIFCOPY sein, um zu bestimmen, ob der zurückgegebene Puffer lesbar, schreibbar oder so eingestellt ist, dass das Originalpuffer aktualisiert wird, wenn eine Kopie erstellt werden muss. Wenn buffertype PyBUF_WRITE ist und der Puffer nicht zusammenhängend ist, wird ein Fehler ausgelöst. In diesem Fall kann der Benutzer PyBUF_UPDATEIFCOPY verwenden, um sicherzustellen, dass ein beschreibbarer temporärer zusammenhängender Puffer zurückgegeben wird. Der Inhalt dieses zusammenhängenden Puffers wird in das ursprüngliche Objekt zurückkopiert, nachdem das memoryview-Objekt gelöscht wurde, solange das ursprüngliche Objekt schreibbar ist. Wenn dies vom ursprünglichen Objekt nicht erlaubt ist, wird ein BufferError ausgelöst.
Wenn das Objekt mehrdimensional ist, dann ist bei fortran = 'F' die erste Dimension des zugrundeliegenden Arrays im Puffer am schnellsten variierend. Wenn fortran = 'C', dann variiert die letzte Dimension am schnellsten (C-Stil zusammenhängend). Wenn fortran = 'A', dann spielt es keine Rolle, und Sie erhalten, was immer das Objekt als effizienter erachtet. Wenn eine Kopie erstellt wird, muss der Speicher durch Aufruf von PyMem_Free freigegeben werden.
Sie erhalten eine neue Referenz auf das memoryview-Objekt.
int PyObject_CopyToObject(PyObject *obj, void *buf, Py_ssize_t len,
char fortran)
Kopiert len Bytes von Daten, die vom zusammenhängenden Speicherblock pointer auf buf zeigten, in den vom Objekt exportierten Puffer. Gibt 0 bei Erfolg zurück und -1 bei Fehler (und löst einen Fehler aus). Wenn das Objekt keinen schreibbaren Puffer hat, wird ein Fehler ausgelöst. Wenn fortran = 'F' ist, werden die Daten bei einem mehrdimensionalen Objekt im Fortran-Stil (erste Dimension variiert am schnellsten) in das Array kopiert. Wenn fortran = 'C', werden die Daten im C-Stil (letzte Dimension variiert am schnellsten) in das Array kopiert. Wenn fortran = 'A', spielt es keine Rolle, und die Kopie wird auf die effizienteste Weise vorgenommen.
int PyObject_CopyData(PyObject *dest, PyObject *src)
Diese letzten drei C-API-Aufrufe ermöglichen eine standardmäßige Methode, Daten in und aus Python-Objekten in zusammenhängende Speicherbereiche zu erhalten, unabhängig davon, wie sie tatsächlich gespeichert sind. Diese Aufrufe verwenden die erweiterte Puffer-Schnittstelle, um ihre Arbeit zu erledigen.
int PyBuffer_IsContiguous(Py_buffer *view, char fortran)
Gibt 1 zurück, wenn der durch das View-Objekt definierte Speicher C-Stil (fortran = 'C') oder Fortran-Stil (fortran = 'F') zusammenhängend oder eines von beiden (fortran = 'A') ist. Gibt andernfalls 0 zurück.
void PyBuffer_FillContiguousStrides(int ndim, Py_ssize_t *shape,
Py_ssize_t *strides, Py_ssize_t itemsize,
char fortran)
Füllt das Strides-Array mit Byte-Strides eines zusammenhängenden (C-Stil, wenn fortran = 'C' oder Fortran-Stil, wenn fortran = 'F') Arrays der gegebenen Form mit der gegebenen Anzahl von Bytes pro Element.
int PyBuffer_FillInfo(Py_buffer *view, void *buf,
Py_ssize_t len, int readonly, int infoflags)
Füllt eine Pufferinformationsstruktur korrekt für einen Exporteur, der nur einen zusammenhängenden Speicherblock von „unsignierten Bytes“ der gegebenen Länge teilen kann. Gibt 0 bei Erfolg und -1 (mit Fehler) bei Fehler zurück.
PyExc_BufferError
Ein neues Fehlerobjekt für die Rückgabe von Pufferfehlern, die auftreten, weil ein Exporteur die Art von Puffer nicht bereitstellen kann, die ein Konsument erwartet. Dies wird auch ausgelöst, wenn ein Konsument einen Puffer von einem Objekt anfordert, das das Protokoll nicht bereitstellt.
Ergänzungen zur String-Syntax der Struktur
Der struct-String-Syntax fehlen einige Zeichen, um Datenformatbeschreibungen vollständig zu implementieren, die bereits an anderer Stelle verfügbar sind (z. B. in ctypes und NumPy). Die Spezifikation von Python 2.5 finden Sie unter https://docs.pythonlang.de/library/struct.html.
Hier sind die vorgeschlagenen Ergänzungen:
| Zeichen | Description |
|---|---|
| ‘t’ | Bit (die Zahl davor gibt die Anzahl der Bits an) |
| ‘?’ | Plattform-Bool-Typ |
| ‘g’ | long double |
| ‘c’ | ucs-1 (latin-1) Kodierung |
| ‘u’ | ucs-2 |
| ‘w’ | ucs-4 |
| ‘O’ | Zeiger auf ein Python-Objekt |
| ‘Z’ | komplex (was auch immer der nächste Spezifizierer ist) |
| ‘&’ | spezifischer Zeiger (Präfix vor einem anderen Zeichen) |
| ‘T{}’ | Struktur (detailliertes Layout in {}) |
| ‘(k1,k2,…,kn)’ | mehrdimensionales Array von allem, was folgt |
| ‘:name:’ | optionaler Name des vorhergehenden Elements |
| ‘X{}’ |
|
Das struct-Modul wird geändert, um diese ebenfalls zu verstehen und entsprechende Python-Objekte beim Entpacken zurückzugeben. Das Entpacken eines long-double gibt ein Dezimalobjekt oder ein ctypes long-double zurück. Das Entpacken von „u“ oder „w“ gibt Python-Unicode zurück. Das Entpacken eines mehrdimensionalen Arrays gibt eine Liste zurück (bei >1D verschachtelt). Das Entpacken eines Zeigers gibt ein ctypes-Zeigerobjekt zurück. Das Entpacken eines Zeiger auf Funktion gibt ein ctypes-Aufrufobjekt zurück (vielleicht). Das Entpacken eines Bits gibt einen Python-Bool zurück. Leerräume in der struct-String-Syntax werden ignoriert, wenn sie nicht bereits ignoriert werden. Das Entpacken eines benannten Objekts gibt ein benanntes Tupel-ähnliches Objekt zurück, das sich wie ein Tupel verhält, dessen Elemente aber auch per Namen zugänglich sind. Das Entpacken einer verschachtelten Struktur gibt ein verschachteltes Tupel zurück.
Endian-Spezifikationen („!“, „@“, „=“, „>“, „<“, „^“) sind ebenfalls innerhalb des Strings erlaubt, damit sie bei Bedarf geändert werden können. Der zuvor angegebene Endian-String ist in Kraft, bis er geändert wird. Das Standard-Endian ist „@“, was native Datentypen und Ausrichtung bedeutet. Wenn nicht-ausgerichtete, native Datentypen angefordert werden, dann ist die Endian-Spezifikation „^“.
Laut dem struct-Modul kann eine Zahl einem Zeichencode vorangestellt werden, um anzugeben, wie viele davon vorhanden sind. Die Erweiterung (k1,k2,...,kn) erlaubt auch die Angabe, ob die Daten als (C-Stil zusammenhängendes, letzte Dimension am schnellsten variierendes) mehrdimensionales Array eines bestimmten Formats betrachtet werden sollen.
Funktionen sollten zu ctypes hinzugefügt werden, um ein ctypes-Objekt aus einer Struktur-Beschreibung zu erstellen und long-double und ucs-2 zu ctypes hinzuzufügen.
Beispiele für Datenformatbeschreibungen
Hier sind einige Beispiele für C-Strukturen und wie sie mit der struct-Stil-Syntax dargestellt würden.
<named> ist der Konstruktor für ein benanntes Tupel (noch nicht spezifiziert).
- float
'd'<–> Python float- komplexer Double
'Zd'<–> Python komplex- RGB Pixeldaten
'BBB'<–> (int, int, int)'B:r: B:g: B:b:'<–> <named>((int, int, int), (‘r’,’g’,’b’))- Gemischter Endian (seltsam, aber möglich)
'>i:big: <i:little:'<–> <named>((int, int), (‘big’, ‘little’))- Verschachtelte Struktur
struct { int ival; struct { unsigned short sval; unsigned char bval; unsigned char cval; } sub; } """i:ival: T{ H:sval: B:bval: B:cval: }:sub: """
- Verschachteltes Array
struct { int ival; double data[16*4]; } """i:ival: (16,4)d:data: """
Beachten Sie, dass die im letzten Beispiel verglichene C-Struktur absichtlich ein 1-D-Array und kein 2-D-Array data[16][4] ist. Der Grund dafür ist, Verwechslungen zwischen statischen mehrdimensionalen Arrays in C (die zusammenhängend angeordnet sind) und dynamischen mehrdimensionalen Arrays zu vermeiden, die dieselbe Syntax verwenden, um auf Elemente zuzugreifen, data[0][1], deren Speicher jedoch nicht notwendigerweise zusammenhängend ist. Die Struct-Syntax verwendet *immer* zusammenhängenden Speicher, und der mehrdimensionale Charakter ist eine Information über den Speicher, die vom Exporteur mitgeteilt werden soll.
Mit anderen Worten, die Beschreibung der Struct-Syntax muss nicht exakt mit der C-Syntax übereinstimmen, solange sie dasselbe Speicherlayout beschreibt. Die Tatsache, dass ein C-Compiler den Speicher als 1-D-Array von Doubles betrachten würde, ist für die Tatsache irrelevant, dass der Exporteur dem Konsumenten mitteilen wollte, dass dieses Speicherfeld als 2-D-Array betrachtet werden sollte, wobei nach jedem 4. Element eine neue Dimension berücksichtigt wird.
Betroffener Code
Alle Objekte und Module in Python, die die alte Puffer-Schnittstelle exportieren oder konsumieren, werden modifiziert. Hier ist eine Teilliste.
- Pufferobjekt
- Bytes-Objekt
- String-Objekt
- Unicode-Objekt
- Array-Modul
- Struct-Modul
- Mmap-Modul
- CTypes-Modul
Alles andere, das die Puffer-API verwendet.
Probleme und Details
Es ist beabsichtigt, dass dieses PEP auf Python 2.6 zurückportiert wird, indem die C-API und die beiden Funktionen zum bestehenden Pufferprotokoll hinzugefügt werden.
Frühere Versionen dieses PEP schlugen ein Lese-/Schreibsperrschema vor, aber es wurde später als a) zu kompliziert für übliche einfache Anwendungsfälle, die keine Sperren erfordern, und b) zu einfach für Anwendungsfälle, die gleichzeitigen Lese-/Schreibzugriff auf einen Puffer mit sich ändernden, kurzlebigen Sperren erforderten, wahrgenommen. Es obliegt daher den Benutzern, ihr eigenes spezifisches Sperrschema um Pufferobjekte zu implementieren, wenn sie konsistente Ansichten über gleichzeitigen Lese-/Schreibzugriff benötigen. Ein zukünftiges PEP kann nach einigen Erfahrungen mit diesen Benutzerschemata vorgeschlagen werden, das eine separate Sperr-API enthält.
Das Teilen von gestrecktem Speicher und Unter-Offsets ist neu und kann als Modifikation der Multi-Segment-Schnittstelle angesehen werden. Es wird von NumPy und der PIL motiviert. NumPy-Objekte sollten in der Lage sein, ihren gestreckten Speicher mit Code zu teilen, der versteht, wie man gestreckten Speicher verwaltet, da gestreckter Speicher beim Umgang mit Rechenbibliotheken sehr häufig vorkommt.
Auch mit diesem Ansatz sollte es möglich sein, generischen Code zu schreiben, der ohne Kopieren mit beiden Arten von Speicher funktioniert.
Die Speicherverwaltung des Formatstrings, des Shape-Arrays, des Strides-Arrays und des Suboffsets-Arrays in der Pufferinfo-Struktur ist immer die Verantwortung des exportierenden Objekts. Der Konsument sollte diese Zeiger nicht auf anderen Speicher setzen oder versuchen, sie freizugeben.
Mehrere Ideen wurden diskutiert und verworfen
Ein "Releaser"-Objekt zu haben, dessen release-buffer aufgerufen wurde. Dies wurde als inakzeptabel angesehen, da es das Protokoll asymmetrisch machte (man rief release auf etwas anderem auf als von dem man den Puffer "erhalten" hat). Es komplizierte auch das Protokoll, ohne einen wirklichen Nutzen zu bieten.Alle Struct-Variablen separat an die Funktion zu übergeben. Dies hatte den Vorteil, dass NULL für nicht interessierende Variablen gesetzt werden konnte, aber es machte den Funktionsaufruf auch schwieriger. Die Flags-Variable ermöglicht es Konsumenten auf dieselbe Weise "einfach" zu sein, wie sie das Protokoll aufrufen.
Code
Die Autoren des PEP versprechen, den Code für diesen Vorschlag beizusteuern und zu pflegen, freuen sich aber über jede Hilfe.
Beispiele
Bsp. 1
Dieses Beispiel zeigt, wie ein Bildobjekt, das zusammenhängende Zeilen verwendet, seinen Puffer exponieren könnte
struct rgba {
unsigned char r, g, b, a;
};
struct ImageObject {
PyObject_HEAD;
...
struct rgba** lines;
Py_ssize_t height;
Py_ssize_t width;
Py_ssize_t shape_array[2];
Py_ssize_t stride_array[2];
Py_ssize_t view_count;
};
„lines“ verweist auf ein mit malloc zugewiesenes 1-D-Array von (struct rgba*). Jeder Zeiger in DIESEM Block verweist auf ein separat mit malloc zugewiesenes Array von (struct rgba).
Um beispielsweise den roten Wert des Pixels bei x=30, y=50 abzurufen, würden Sie „lines[50][30].r“ verwenden.
Was macht also ImageObject's getbuffer? Fehlerprüfung weggelassen
int Image_getbuffer(PyObject *self, Py_buffer *view, int flags) {
static Py_ssize_t suboffsets[2] = { 0, -1};
view->buf = self->lines;
view->len = self->height*self->width;
view->readonly = 0;
view->ndims = 2;
self->shape_array[0] = height;
self->shape_array[1] = width;
view->shape = &self->shape_array;
self->stride_array[0] = sizeof(struct rgba*);
self->stride_array[1] = sizeof(struct rgba);
view->strides = &self->stride_array;
view->suboffsets = suboffsets;
self->view_count ++;
return 0;
}
int Image_releasebuffer(PyObject *self, Py_buffer *view) {
self->view_count--;
return 0;
}
Bsp. 2
Dieses Beispiel zeigt, wie ein Objekt, das einen zusammenhängenden Speicherblock exponieren möchte (der nicht neu zugewiesen wird, solange das Objekt lebt), dies tun würde.
int myobject_getbuffer(PyObject *self, Py_buffer *view, int flags) {
void *buf;
Py_ssize_t len;
int readonly=0;
buf = /* Point to buffer */
len = /* Set to size of buffer */
readonly = /* Set to 1 if readonly */
return PyObject_FillBufferInfo(view, buf, len, readonly, flags);
}
/* No releasebuffer is necessary because the memory will never
be re-allocated
*/
Bsp. 3
Ein Konsument, der nur einen einfachen, zusammenhängenden Byteblock von einem Python-Objekt erhalten möchte, obj würde Folgendes tun
Py_buffer view;
int ret;
if (PyObject_GetBuffer(obj, &view, Py_BUF_SIMPLE) < 0) {
/* error return */
}
/* Now, view.buf is the pointer to memory
view.len is the length
view.readonly is whether or not the memory is read-only.
*/
/* After using the information and you don't need it anymore */
if (PyBuffer_Release(obj, &view) < 0) {
/* error return */
}
Bsp. 4
Ein Konsument, der in der Lage sein möchte, den Speicher jedes Objekts zu verwenden, aber einen Algorithmus schreibt, der nur zusammenhängenden Speicher verarbeitet, könnte Folgendes tun
void *buf;
Py_ssize_t len;
char *format;
int copy;
copy = PyObject_GetContiguous(obj, &buf, &len, &format, 0, 'A');
if (copy < 0) {
/* error return */
}
/* process memory pointed to by buffer if format is correct */
/* Optional:
if, after processing, we want to copy data from buffer back
into the object
we could do
*/
if (PyObject_CopyToObject(obj, buf, len, 'A') < 0) {
/* error return */
}
/* Make sure that if a copy was made, the memory is freed */
if (copy == 1) PyMem_Free(buf);
Urheberrecht
Dieses PEP wird gemeinfrei gestellt.
Quelle: https://github.com/python/peps/blob/main/peps/pep-3118.rst
Zuletzt geändert: 2025-02-14 08:06:03 GMT