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
Zusammenfassung
Fügen Sie Funktionen zur begrenzten C-API-Version 3.14 hinzu
PyUnicode_Export(): Exportiert ein Python-str-Objekt alsPy_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ürPyUnicode_1BYTE_KIND.PyUnicode_2BYTE_DATA()fürPyUnicode_2BYTE_KIND.PyUnicode_4BYTE_DATA()fürPyUnicode_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
-1zurü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
NULLzurü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
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
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-0756.rst
Zuletzt geändert: 2025-02-01 07:28:42 GMT