Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python Enhancement Proposals

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

Inhaltsverzeichnis

Wichtig

Dieses PEP ist ein historisches Dokument. Die aktuelle, kanonische Dokumentation finden Sie nun unter der PyBytesWriter API.

×

Siehe PEP 1, um Änderungen vorzuschlagen.

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 bytes Writer-Instanz, die von PyBytesWriter_Create() erstellt wurde.

Die Instanz muss mit PyBytesWriter_Finish() oder PyBytesWriter_Discard() zerstört werden.

Erstellen, Abschließen, Verwerfen

PyBytesWriter *PyBytesWriter_Create(Py_ssize_t size)
Erstellt einen PyBytesWriter zum 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() erstellten PyBytesWriter ab.

Im Erfolgsfall wird ein Python bytes Objekt zurückgegeben. Im Fehlerfall wird eine Ausnahme gesetzt und NULL zurü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 des bytes Objekts auf size Bytes geändert.
PyObject *PyBytesWriter_FinishWithPointer(PyBytesWriter *writer, void *buf)
Ähnlich wie PyBytesWriter_Finish(), aber der Writer wird vor der Erstellung des bytes Objekts 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() erstellten PyBytesWriter.

Tut nichts, wenn writer NULL ist.

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 -1 ist, wird strlen(bytes) aufgerufen, um die Stringlänge zu ermitteln.

Im Erfolgsfall wird 0 zurückgegeben. Im Fehlerfall wird eine Ausnahme gesetzt und -1 zurü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 0 zurückgegeben. Im Fehlerfall wird eine Ausnahme gesetzt und -1 zurü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() oder PyBytesWriter_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 0 zurückgegeben. Im Fehlerfall wird eine Ausnahme gesetzt und -1 zurü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 0 zurückgegeben. Im Fehlerfall wird eine Ausnahme gesetzt und -1 zurü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 NULL sein.

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

Pull Request gh-131681.

Hinweise zur CPython-Referenzimplementierung, die nicht Teil der Spezifikation sind

  • Die Implementierung allokiert intern ein bytes Objekt, sodass PyBytesWriter_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 bytes Objekts. Am Ende erstellt PyBytesWriter_Finish() das bytes Objekt aus diesem kleinen Puffer.
  • Eine Free-Liste wird verwendet, um die Kosten für die Allokation eines PyBytesWriter im 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


Quelle: https://github.com/python/peps/blob/main/peps/pep-0782.rst

Zuletzt geändert: 2025-09-18 13:28:58 GMT