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

Python Enhancement Proposals

PEP 756 – Fügen Sie PyUnicode_Export() und PyUnicode_Import() C-Funktionen hinzu

Autor:
Victor Stinner <vstinner at python.org>
PEP-Delegate:
C API Arbeitsgruppe
Discussions-To:
Discourse thread
Status:
Zurückgezogen
Typ:
Standards Track
Erstellt:
13. Sep. 2024
Python-Version:
3.14
Post-History:
14. Sep. 2024
Resolution:
29. Okt. 2024

Inhaltsverzeichnis

Zusammenfassung

Fügen Sie Funktionen zur begrenzten C-API-Version 3.14 hinzu

  • PyUnicode_Export(): Exportiert ein Python-str-Objekt als Py_buffer-Ansicht.
  • PyUnicode_Import(): Importiert ein Python-str-Objekt.

Auf CPython hat PyUnicode_Export() eine Komplexität von O(1): Es wird kein Speicher kopiert und keine Konvertierung durchgeführt.

Begründung

PEP 393

PEP 393 „Flexible String Representation“ änderte die internen Zeichenfolgen in Python 3.3, um drei Formate zu verwenden

  • PyUnicode_1BYTE_KIND: Unicode-Bereich [U+0000; U+00ff], UCS-1, 1 Byte/Zeichen.
  • PyUnicode_2BYTE_KIND: Unicode-Bereich [U+0000; U+ffff], UCS-2, 2 Bytes/Zeichen.
  • PyUnicode_4BYTE_KIND: Unicode-Bereich [U+0000; U+10ffff], UCS-4, 4 Bytes/Zeichen.

Ein Python-str-Objekt muss immer das kompakteste Format verwenden. Beispielsweise muss eine Zeichenfolge, die nur ASCII-Zeichen enthält, das UCS-1-Format verwenden.

Die Funktion PyUnicode_KIND() kann verwendet werden, um das von einer Zeichenfolge verwendete Format zu ermitteln.

Eine der folgenden Funktionen kann zum Zugriff auf Daten verwendet werden

  • PyUnicode_1BYTE_DATA() für PyUnicode_1BYTE_KIND.
  • PyUnicode_2BYTE_DATA() für PyUnicode_2BYTE_KIND.
  • PyUnicode_4BYTE_DATA() für PyUnicode_4BYTE_KIND.

Um die beste Leistung zu erzielen, sollte eine C-Erweiterung 3 Code-Pfade für jedes dieser 3 nativen Zeichenfolgenformate haben.

Begrenzte C-API

PEP 393-Funktionen wie PyUnicode_KIND() und PyUnicode_1BYTE_DATA() sind von der begrenzten C-API ausgeschlossen. Es ist nicht möglich, für UCS-Formate spezialisierten Code zu schreiben. Eine C-Erweiterung, die die begrenzte C-API verwendet, kann nur weniger effiziente Code-Pfade und Zeichenfolgenformate verwenden.

Beispielsweise verfügt das Projekt MarkupSafe über eine C-Erweiterung, die für UCS-Formate für beste Leistung optimiert ist, und kann daher die begrenzte C-API nicht verwenden.

Spezifikation

API

Fügen Sie die folgende API zur begrenzten C-API-Version 3.14 hinzu

int32_t PyUnicode_Export(
    PyObject *unicode,
    int32_t requested_formats,
    Py_buffer *view);
PyObject* PyUnicode_Import(
    const void *data,
    Py_ssize_t nbytes,
    int32_t format);

#define PyUnicode_FORMAT_UCS1  0x01   // Py_UCS1*
#define PyUnicode_FORMAT_UCS2  0x02   // Py_UCS2*
#define PyUnicode_FORMAT_UCS4  0x04   // Py_UCS4*
#define PyUnicode_FORMAT_UTF8  0x08   // char*
#define PyUnicode_FORMAT_ASCII 0x10   // char* (ASCII string)

Der Typ int32_t wird anstelle von int verwendet, um eine klar definierte Plattformerfassung zu gewährleisten und nicht von der Plattform oder dem Compiler abhängig zu sein. Siehe Vermeiden von C-spezifischen Typen für die ausführlichere Begründung.

PyUnicode_Export()

API

int32_t PyUnicode_Export(
    PyObject *unicode,
    int32_t requested_formats,
    Py_buffer *view)

Exportiert den Inhalt der unicode-Zeichenfolge in einem der requested_formats.

  • Bei Erfolg wird view gefüllt und ein Format (größer als 0) zurückgegeben.
  • Bei einem Fehler wird eine Ausnahme ausgelöst und -1 zurückgegeben. view bleibt unverändert.

Nach einem erfolgreichen Aufruf von PyUnicode_Export() muss der view-Puffer mit PyBuffer_Release() freigegeben werden. Der Inhalt des Puffers ist gültig, bis er freigegeben wird.

Der Puffer ist schreibgeschützt und darf nicht geändert werden.

Das Mitglied view->len muss verwendet werden, um die Stringlänge zu ermitteln. Der Puffer sollte mit einem nachgestellten NUL-Zeichen enden, aber es wird nicht empfohlen, sich darauf zu verlassen, da eingebettete NUL-Zeichen vorhanden sein können.

unicode und view dürfen nicht NULL sein.

Verfügbare Formate

Konstantenbezeichner Wert Description
PyUnicode_FORMAT_UCS1 0x01 UCS-1-Zeichenfolge (Py_UCS1*)
PyUnicode_FORMAT_UCS2 0x02 UCS-2-Zeichenfolge (Py_UCS2*)
PyUnicode_FORMAT_UCS4 0x04 UCS-4-Zeichenfolge (Py_UCS4*)
PyUnicode_FORMAT_UTF8 0x08 UTF-8-Zeichenfolge (char*)
PyUnicode_FORMAT_ASCII 0x10 ASCII-Zeichenfolge (Py_UCS1*)

UCS-2 und UCS-4 verwenden die native Byte-Reihenfolge.

requested_formats kann ein einzelnes Format oder eine bitweise Kombination der Formate in der obigen Tabelle sein. Bei Erfolg wird das zurückgegebene Format auf ein einzelnes der angeforderten Formate gesetzt.

Beachten Sie, dass zukünftige Versionen von Python zusätzliche Formate einführen können.

Es wird kein Speicher kopiert und keine Konvertierung durchgeführt.

Export-Komplexität

Auf CPython hat ein Export eine Komplexität von O(1): Es wird kein Speicher kopiert und keine Konvertierung durchgeführt.

Um die beste Leistung auf CPython und PyPy zu erzielen, wird empfohlen, diese 4 Formate zu unterstützen

(PyUnicode_FORMAT_UCS1 \
 | PyUnicode_FORMAT_UCS2 \
 | PyUnicode_FORMAT_UCS4 \
 | PyUnicode_FORMAT_UTF8)

PyPy verwendet nativ UTF-8 und daher wird das Format PyUnicode_FORMAT_UTF8 empfohlen. Es erfordert eine Speicherkopie, da PyPy str-Objekte im Speicher verschoben werden können (PyPy verwendet einen Moving Garbage Collector).

Py_buffer Format und Elementgröße

Py_buffer verwendet das folgende Format und die folgende Elementgröße, abhängig vom Exportformat

Exportformat Pufferformat Elementgröße
PyUnicode_FORMAT_UCS1 "B" 1 Byte
PyUnicode_FORMAT_UCS2 "=H" 2 Bytes
PyUnicode_FORMAT_UCS4 "=I" 4 Bytes
PyUnicode_FORMAT_UTF8 "B" 1 Byte
PyUnicode_FORMAT_ASCII "B" 1 Byte

PyUnicode_Import()

API

PyObject* PyUnicode_Import(
    const void *data,
    Py_ssize_t nbytes,
    int32_t format)

Erstellt ein Unicode-Zeichenfolgenobjekt aus einem Puffer in einem unterstützten Format.

  • Gibt bei Erfolg einen Verweis auf ein neues Zeichenfolgenobjekt zurück.
  • Löst bei einem Fehler eine Ausnahme aus und gibt NULL zurück.

data darf nicht NULL sein. nbytes muss positiv oder null sein.

Siehe PyUnicode_Export() für die verfügbaren Formate.

UTF-8-Format

CPython 3.14 verwendet das UTF-8-Format nicht intern und unterstützt nicht den Export einer Zeichenfolge als UTF-8. Die Funktion PyUnicode_AsUTF8AndSize() kann stattdessen verwendet werden.

Das Format PyUnicode_FORMAT_UTF8 wird zur Kompatibilität mit alternativen Implementierungen bereitgestellt, die möglicherweise nativ UTF-8 für Zeichenfolgen verwenden.

ASCII-Format

Wenn das Format PyUnicode_FORMAT_ASCII für den Export angefordert wird, wird das Exportformat PyUnicode_FORMAT_UCS1 für ASCII-Zeichenfolgen verwendet.

Das Format PyUnicode_FORMAT_ASCII ist hauptsächlich für PyUnicode_Import() nützlich, um zu validieren, dass eine Zeichenfolge nur ASCII-Zeichen enthält.

Surrogate-Zeichen und eingebettete NUL-Zeichen

Surrogate-Zeichen sind erlaubt: sie können importiert und exportiert werden.

Eingebettete NUL-Zeichen sind erlaubt: sie können importiert und exportiert werden.

Implementierung

https://github.com/python/cpython/pull/123738

Abwärtskompatibilität

Es gibt keine Auswirkungen auf die Abwärtskompatibilität, es werden nur neue C-API-Funktionen hinzugefügt.

Verwendung von PEP 393 C-APIs

Eine Code-Suche in den Top 7.500 Projekten auf PyPI (im März 2024) zeigt, dass viele Projekte UCS-Formate mit der regulären C-API importieren und exportieren.

PyUnicode_FromKindAndData()

25 Projekte rufen PyUnicode_FromKindAndData() auf

  • Cython (3.0.9)
  • Levenshtein (0.25.0)
  • PyICU (2.12)
  • PyICU-binary (2.7.4)
  • PyQt5 (5.15.10)
  • PyQt6 (6.6.1)
  • aiocsv (1.3.1)
  • asyncpg (0.29.0)
  • biopython (1.83)
  • catboost (1.2.3)
  • cffi (1.16.0)
  • mojimoji (0.0.13)
  • mwparserfromhell (0.6.6)
  • numba (0.59.0)
  • numpy (1.26.4)
  • orjson (3.9.15)
  • pemja (0.4.1)
  • pyahocorasick (2.0.0)
  • pyjson5 (1.6.6)
  • rapidfuzz (3.6.2)
  • regex (2023.12.25)
  • srsly (2.4.8)
  • tokenizers (0.15.2)
  • ujson (5.9.0)
  • unicodedata2 (15.1.0)

PyUnicode_4BYTE_DATA()

21 Projekte rufen PyUnicode_2BYTE_DATA() und/oder PyUnicode_4BYTE_DATA() auf

  • Cython (3.0.9)
  • MarkupSafe (2.1.5)
  • Nuitka (2.1.2)
  • PyICU (2.12)
  • PyICU-binary (2.7.4)
  • PyQt5_sip (12.13.0)
  • PyQt6_sip (13.6.0)
  • biopython (1.83)
  • catboost (1.2.3)
  • cement (3.0.10)
  • cffi (1.16.0)
  • duckdb (0.10.0)
  • mypy (1.9.0)
  • numpy (1.26.4)
  • orjson (3.9.15)
  • pemja (0.4.1)
  • pyahocorasick (2.0.0)
  • pyjson5 (1.6.6)
  • pyobjc-core (10.2)
  • sip (6.8.3)
  • wxPython (4.2.1)

Abgelehnte Ideen

Verwerfen Sie eingebettete NUL-Zeichen und verlangen Sie ein nachgestelltes NUL-Zeichen

In C ist es praktisch, ein nachgestelltes NUL-Zeichen zu haben. Zum Beispiel kann die Schleife for (; *str != 0; str++) verwendet werden, um über Zeichen zu iterieren, und strlen() kann verwendet werden, um die Länge einer Zeichenfolge zu ermitteln.

Das Problem ist, dass ein Python-str-Objekt NUL-Zeichen einbetten kann. Beispiel: "ab\0c". Wenn eine Zeichenfolge ein eingebettetes NUL-Zeichen enthält, schneidet Code, der sich auf das NUL-Zeichen zum Finden des String-Endes verlässt, die Zeichenfolge ab. Dies kann zu Fehlern oder sogar Sicherheitslücken führen. Siehe eine frühere Diskussion in der Ausgabe Ändern Sie PyUnicode_AsUTF8(), um NULL bei eingebetteten Nullzeichen zurückzugeben.

Das Verwerfen von eingebetteten NUL-Zeichen erfordert das Scannen der Zeichenfolge, was eine Komplexität von O(n) hat.

Verwerfen Sie Surrogate-Zeichen

Surrogate-Zeichen sind Zeichen im Unicode-Bereich [U+D800; U+DFFF]. Sie sind von UTF-Codecs wie UTF-8 nicht zugelassen. Ein Python-str-Objekt kann beliebige einzelne Surrogate-Zeichen enthalten. Beispiel: "\uDC80".

Das Verwerfen von Surrogate-Zeichen verhindert den Export einer Zeichenfolge, die ein solches Zeichen enthält. Dies kann überraschend und ärgerlich sein, da der Aufrufer von PyUnicode_Export() den Inhalt der Zeichenfolge nicht kontrolliert.

Das Zulassen von Surrogate-Zeichen ermöglicht den Export jeder Zeichenfolge und vermeidet so dieses Problem. Zum Beispiel kann der UTF-8-Codec mit dem Fehlerbehandlungsmechanismus surrogatepass verwendet werden, um Surrogate-Zeichen zu kodieren und zu dekodieren.

Konvertierungen bei Bedarf

Es wäre praktisch, Formate bei Bedarf zu konvertieren. Zum Beispiel UCS-1 und UCS-2 nach UCS-4 zu konvertieren, wenn nur ein Export nach UCS-4 angefordert wird.

Das Problem ist, dass die meisten Benutzer erwarten, dass ein Export keine Speicherkopie und keine Konvertierung erfordert: eine Komplexität von O(1). Es ist besser, eine API zu haben, bei der alle Operationen eine Komplexität von O(1) haben.

Export nach UTF-8

CPython 3.14 verfügt über einen Cache zum Kodieren einer Zeichenfolge in UTF-8. Es ist verlockend, den Export nach UTF-8 zu gestatten.

Das Problem ist, dass der UTF-8-Cache keine Surrogate-Zeichen unterstützt. Ein Export wird erwartet, den gesamten Zeichenfolgeninhalt bereitzustellen, einschließlich eingebetteter NUL-Zeichen und Surrogate-Zeichen. Um Surrogate-Zeichen zu exportieren, ist ein anderer Code-Pfad erforderlich, der den Fehlerbehandlungsmechanismus surrogatepass verwendet, und jede Exportoperation muss einen temporären Puffer zuweisen: Komplexität O(n).

Ein Export wird mit einer Komplexität von O(1) erwartet, daher wurde die Idee, UTF-8 in CPython zu exportieren, aufgegeben.

Diskussionen


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

Zuletzt geändert: 2025-02-01 07:28:42 GMT