PEP 782 – Füge PyBytesWriter C API hinzu
- Autor:
- Victor Stinner <vstinner at python.org>
- Discussions-To:
- Discourse thread
- Status:
- Final
- Typ:
- Standards Track
- Erstellt:
- 27-März 2025
- Python-Version:
- 3.15
- Post-History:
- 18-Feb-2025
- Resolution:
- 11-Sep-2025
Zusammenfassung
Füge eine neue PyBytesWriter C API hinzu, um bytes Objekte zu erstellen.
Soft-Deprecated sind die APIs PyBytes_FromStringAndSize(NULL, size) und _PyBytes_Resize(). Diese APIs behandeln ein unveränderliches bytes Objekt als veränderlich. Sie bleiben verfügbar und werden gewartet, lösen keine Deprecation-Warnung aus, werden aber bei neuem Code nicht mehr empfohlen.
Begründung
Erstellung unvollständiger/inkonsistenter Objekte verhindern
Das Erstellen eines Python bytes Objekts mit PyBytes_FromStringAndSize(NULL, size) und _PyBytes_Resize() behandelt ein unveränderliches bytes Objekt als veränderlich. Dies widerspricht dem Prinzip, dass bytes Objekte unveränderlich sind. Außerdem wird ein unvollständiges oder „ungültiges“ Objekt erstellt, da Bytes nicht initialisiert sind. In Python sollte ein bytes Objekt immer vollständig initialisierte Bytes haben.
Ineffiziente Allokationsstrategie
Beim Erstellen eines Byte-Strings, wenn die Ausgabegröße unbekannt ist, besteht eine Strategie darin, einen kurzen Puffer zuzuweisen und diesen (auf die exakte Größe) zu erweitern, jedes Mal wenn ein größerer Schreibvorgang benötigt wird.
Diese Strategie ist ineffizient, da der Puffer mehrmals vergrößert werden muss. Es ist effizienter, den Puffer beim ersten Mal, wenn ein größerer Schreibvorgang benötigt wird, zu überdimensionieren. Dies reduziert die Anzahl kostspieliger realloc() Operationen, die eine Speicher kopie implizieren können.
Spezifikation
API
-
type PyBytesWriter
- Eine Python
bytesWriter-Instanz, die vonPyBytesWriter_Create()erstellt wurde.Die Instanz muss mit
PyBytesWriter_Finish()oderPyBytesWriter_Discard()zerstört werden.
Erstellen, Abschließen, Verwerfen
-
PyBytesWriter *PyBytesWriter_Create(Py_ssize_t size)
- Erstellt einen
PyBytesWriterzum Schreiben von size Bytes.Wenn size größer als Null ist, werden size Bytes allokiert und die Writer-Größe auf size gesetzt. Der Aufrufer ist dafür verantwortlich, size Bytes mit
PyBytesWriter_GetData()zu schreiben.Im Fehlerfall wird eine Ausnahme gesetzt und NULL zurückgegeben.
size muss positiv oder null sein.
-
PyObject *PyBytesWriter_Finish(PyBytesWriter *writer)
- Schließt einen von
PyBytesWriter_Create()erstelltenPyBytesWriterab.Im Erfolgsfall wird ein Python
bytesObjekt zurückgegeben. Im Fehlerfall wird eine Ausnahme gesetzt undNULLzurückgegeben.Die Writer-Instanz ist nach dem Aufruf in jedem Fall ungültig.
-
PyObject *PyBytesWriter_FinishWithSize(PyBytesWriter *writer, Py_ssize_t size)
- Ähnlich wie
PyBytesWriter_Finish(), aber der Writer wird vor der Erstellung desbytesObjekts auf size Bytes geändert.
-
PyObject *PyBytesWriter_FinishWithPointer(PyBytesWriter *writer, void *buf)
- Ähnlich wie
PyBytesWriter_Finish(), aber der Writer wird vor der Erstellung desbytesObjekts anhand des buf-Zeigers geändert.Im Fehlerfall wird eine Ausnahme gesetzt und NULL zurückgegeben, wenn der buf-Zeiger außerhalb der Grenzen des internen Puffers liegt.
Funktions-Pseudocode
Py_ssize_t size = (char*)buf - (char*)PyBytesWriter_GetData(writer); return PyBytesWriter_FinishWithSize(writer, size);
-
void PyBytesWriter_Discard(PyBytesWriter *writer)
- Verwirft einen von
PyBytesWriter_Create()erstelltenPyBytesWriter.Tut nichts, wenn writer
NULList.Die Writer-Instanz ist nach dem Aufruf ungültig.
High-Level API
-
int PyBytesWriter_WriteBytes(PyBytesWriter *writer, const void *bytes, Py_ssize_t size)
- Vergrößert den internen Puffer des writer um size Bytes, schreibt size Bytes von bytes an das Ende des writer und addiert size zur Größe des writer.
Wenn size gleich
-1ist, wirdstrlen(bytes)aufgerufen, um die Stringlänge zu ermitteln.Im Erfolgsfall wird
0zurückgegeben. Im Fehlerfall wird eine Ausnahme gesetzt und-1zurückgegeben.
-
int PyBytesWriter_Format(PyBytesWriter *writer, const char *format, ...)
- Ähnlich wie
PyBytes_FromFormat(), aber die Ausgabe wird direkt an das Ende des Writers geschrieben. Der interne Puffer des Writers wird bei Bedarf vergrößert. Anschließend wird die geschriebene Größe zur Writer-Größe hinzugefügt.Im Erfolgsfall wird
0zurückgegeben. Im Fehlerfall wird eine Ausnahme gesetzt und-1zurückgegeben.
Getter
-
Py_ssize_t PyBytesWriter_GetSize(PyBytesWriter *writer)
- Gibt die Größe des Writers zurück.
-
void *PyBytesWriter_GetData(PyBytesWriter *writer)
- Gibt die Daten des Writers zurück: Start des internen Puffers.
Der Zeiger ist gültig, bis
PyBytesWriter_Finish()oderPyBytesWriter_Discard()auf writer aufgerufen wird.
Low-Level API
-
int PyBytesWriter_Resize(PyBytesWriter *writer, Py_ssize_t size)
- Ändert die Größe des Writers auf size Bytes. Kann zum Vergrößern oder Verkleinern des Writers verwendet werden.
Neu allokierte Bytes bleiben uninitialisiert.
Im Erfolgsfall wird
0zurückgegeben. Im Fehlerfall wird eine Ausnahme gesetzt und-1zurückgegeben.size muss positiv oder null sein.
-
int PyBytesWriter_Grow(PyBytesWriter *writer, Py_ssize_t grow)
- Ändert die Größe des Writers, indem grow Bytes zur aktuellen Writer-Größe hinzugefügt werden.
Neu allokierte Bytes bleiben uninitialisiert.
Im Erfolgsfall wird
0zurückgegeben. Im Fehlerfall wird eine Ausnahme gesetzt und-1zurückgegeben.size kann negativ sein, um den Writer zu verkleinern.
-
void *PyBytesWriter_GrowAndUpdatePointer(PyBytesWriter *writer, Py_ssize_t size, void *buf)
- Ähnlich wie
PyBytesWriter_Grow(), aber aktualisiert auch den buf-Zeiger.Der buf-Zeiger wird verschoben, wenn der interne Puffer im Speicher verschoben wird. Die relative Position von buf innerhalb des internen Puffers bleibt unverändert.
Im Fehlerfall wird eine Ausnahme gesetzt und NULL zurückgegeben.
buf darf nicht
NULLsein.Funktions-Pseudocode
Py_ssize_t pos = (char*)buf - (char*)PyBytesWriter_GetData(writer); if (PyBytesWriter_Grow(writer, size) < 0) { return NULL; } return (char*)PyBytesWriter_GetData(writer) + pos;
Überallokation
PyBytesWriter_Resize() und PyBytesWriter_Grow() überdimensionieren den internen Puffer, um die Anzahl der realloc() Aufrufe zu reduzieren und somit Speicher kopien zu minimieren.
PyBytesWriter_Finish() trimmt Überallokationen: Es verkleinert den internen Puffer auf die exakte Größe, wenn das endgültige bytes Objekt erstellt wird.
Thread-Sicherheit
Die API ist nicht threadsicher: Ein Writer sollte immer nur von einem einzigen Thread gleichzeitig verwendet werden.
Soft Deprecations
Soft-Deprecated sind die APIs PyBytes_FromStringAndSize(NULL, size) und _PyBytes_Resize(). Diese APIs behandeln ein unveränderliches bytes Objekt als veränderlich. Sie bleiben verfügbar und werden gewartet, lösen keine Deprecation-Warnung aus, werden aber bei neuem Code nicht mehr empfohlen.
PyBytes_FromStringAndSize(str, size) ist nicht soft deprecated. Nur Aufrufe mit NULL str sind soft deprecated.
Beispiele
High-Level API
Erstelle den Bytes-String b"Hello World!"
PyObject* hello_world(void)
{
PyBytesWriter *writer = PyBytesWriter_Create(0);
if (writer == NULL) {
goto error;
}
if (PyBytesWriter_WriteBytes(writer, "Hello", -1) < 0) {
goto error;
}
if (PyBytesWriter_Format(writer, " %s!", "World") < 0) {
goto error;
}
return PyBytesWriter_Finish(writer);
error:
PyBytesWriter_Discard(writer);
return NULL;
}
Erstelle den Bytes-String „abc“
Beispiel für die Erstellung des Bytes-Strings b"abc" mit einer festen Größe von 3 Bytes
PyObject* create_abc(void)
{
PyBytesWriter *writer = PyBytesWriter_Create(3);
if (writer == NULL) {
return NULL;
}
char *str = PyBytesWriter_GetData(writer);
memcpy(str, "abc", 3);
return PyBytesWriter_Finish(writer);
}
GrowAndUpdatePointer() Beispiel
Beispiel für die Verwendung eines Zeigers zum Schreiben von Bytes und zur Verfolgung der geschriebenen Größe.
Erstelle den Bytes-String b"Hello World"
PyObject* grow_example(void)
{
// Allocate 10 bytes
PyBytesWriter *writer = PyBytesWriter_Create(10);
if (writer == NULL) {
return NULL;
}
// Write some bytes
char *buf = PyBytesWriter_GetData(writer);
memcpy(buf, "Hello ", strlen("Hello "));
buf += strlen("Hello ");
// Allocate 10 more bytes
buf = PyBytesWriter_GrowAndUpdatePointer(writer, 10, buf);
if (buf == NULL) {
PyBytesWriter_Discard(writer);
return NULL;
}
// Write more bytes
memcpy(buf, "World", strlen("World"));
buf += strlen("World");
// Truncate the string at 'buf' position
// and create a bytes object
return PyBytesWriter_FinishWithPointer(writer, buf);
}
Aktualisiere PyBytes_FromStringAndSize() Code
Beispiel für Code, der die soft deprecated API PyBytes_FromStringAndSize(NULL, size) verwendet
PyObject *result = PyBytes_FromStringAndSize(NULL, num_bytes);
if (result == NULL) {
return NULL;
}
if (copy_bytes(PyBytes_AS_STRING(result), start, num_bytes) < 0) {
Py_CLEAR(result);
}
return result;
Dies kann nun aktualisiert werden zu
PyBytesWriter *writer = PyBytesWriter_Create(num_bytes);
if (writer == NULL) {
return NULL;
}
if (copy_bytes(PyBytesWriter_GetData(writer), start, num_bytes) < 0) {
PyBytesWriter_Discard(writer);
return NULL;
}
return PyBytesWriter_Finish(writer);
Aktualisiere _PyBytes_Resize() Code
Beispiel für Code, der die soft deprecated API _PyBytes_Resize() verwendet
PyObject *v = PyBytes_FromStringAndSize(NULL, size);
if (v == NULL) {
return NULL;
}
char *p = PyBytes_AS_STRING(v);
// ... fill bytes into 'p' ...
if (_PyBytes_Resize(&v, (p - PyBytes_AS_STRING(v)))) {
return NULL;
}
return v;
Dies kann nun aktualisiert werden zu
PyBytesWriter *writer = PyBytesWriter_Create(size);
if (writer == NULL) {
return NULL;
}
char *p = PyBytesWriter_GetData(writer);
// ... fill bytes into 'p' ...
return PyBytesWriter_FinishWithPointer(writer, p);
Referenzimplementierung
Hinweise zur CPython-Referenzimplementierung, die nicht Teil der Spezifikation sind
- Die Implementierung allokiert intern ein
bytesObjekt, sodassPyBytesWriter_Finish()das Objekt einfach zurückgibt, ohne Speicher kopieren zu müssen. - Für Strings bis zu 256 Bytes wird ein kleiner interner Raw-Puffer aus Bytes verwendet. Dies vermeidet das unleserliche Ändern eines
bytesObjekts. Am Ende erstelltPyBytesWriter_Finish()dasbytesObjekt aus diesem kleinen Puffer. - Eine Free-Liste wird verwendet, um die Kosten für die Allokation eines
PyBytesWriterim Heap-Speicher zu reduzieren.
Abwärtskompatibilität
Es gibt keine Auswirkung auf die Rückwärtskompatibilität, es werden nur neue APIs hinzugefügt.
Die APIs PyBytes_FromStringAndSize(NULL, size) und _PyBytes_Resize() sind soft deprecated. Beim Verwenden dieser Funktionen werden keine neuen Warnungen ausgegeben und ihre Entfernung ist nicht geplant.
Vorherige Diskussionen
- März 2025: Dritter öffentlicher API-Versuch, Verwendung von Größen statt Zeigern
- Februar 2025: Zweiter öffentlicher API-Versuch
- Juli 2024: Erster öffentlicher API-Versuch
- Entscheidung der C API Working Group: Hinzufügen der PyBytes_Writer() API (August 2024)
- Pull Request gh-121726: Erster öffentlicher API-Versuch (Juli 2024)
- März 2016: Schnelle _PyAccu, _PyUnicodeWriter und _PyBytesWriter APIs zur Erzeugung von Strings in CPython: Artikel über die ursprüngliche private
_PyBytesWriterC API.
Urheberrecht
Dieses Dokument wird in die Public Domain oder unter die CC0-1.0-Universal-Lizenz gestellt, je nachdem, welche Lizenz permissiver ist.
Quelle: https://github.com/python/peps/blob/main/peps/pep-0782.rst
Zuletzt geändert: 2025-09-18 13:28:58 GMT