PEP 296 – Hinzufügen eines bytes-Objekttyps
- Autor:
- Scott Gilbert <xscottg at yahoo.com>
- Status:
- Zurückgezogen
- Typ:
- Standards Track
- Erstellt:
- 12. Juli 2002
- Python-Version:
- 2.3
- Post-History:
Ankündigung
Diese PEP wurde vom Autor zurückgezogen (zugunsten von PEP 358).
Zusammenfassung
Diese PEP schlägt die Erstellung eines neuen Standardtyps und einer integrierten Konstruktorfunktion namens „bytes“ vor. Das bytes-Objekt ist ein effizient gespeichertes Array von Bytes mit einigen zusätzlichen Merkmalen, die es von mehreren ähnlichen Implementierungen unterscheiden.
Begründung
Python hat derzeit viele Objekte, die etwas Ähnliches wie das in diesem Vorschlag vorgesehene bytes-Objekt implementieren. Zum Beispiel sind die Standard-String-, Puffer-, Array- und mmap-Objekte in mancher Hinsicht dem bytes-Objekt sehr ähnlich. Darüber hinaus haben mehrere bedeutende Drittanbieter-Erweiterungen ähnliche Objekte erstellt, um ähnliche Bedürfnisse zu erfüllen. Frustrierenderweise ist jedes dieser Objekte zu eng gefasst und es fehlen entscheidende Funktionen, um es für eine breitere Kategorie von Problemen anwendbar zu machen.
Spezifikation
Das bytes-Objekt hat die folgenden wichtigen Merkmale
- Effiziente zugrunde liegende Array-Speicherung über den Standard-C-Typ „unsigned char“. Dies ermöglicht eine Feinsteuerung der allozierten Speichermenge. Mit den im nächsten Punkt genannten Ausrichtungsbeschränkungen ist es für Low-Level-Erweiterungen trivial, den Zeiger nach Bedarf in einen anderen Typ umzuwandeln.
Da das Objekt als Array von Bytes implementiert ist, ist es auch möglich, das bytes-Objekt an die umfangreiche Bibliothek von Routinen weiterzugeben, die bereits in der Standardbibliothek vorhanden sind und derzeit mit Strings arbeiten. Zum Beispiel könnte das bytes-Objekt in Verbindung mit dem struct-Modul verwendet werden, um eine vollständige Ersetzung für das array-Modul zu bieten, das nur über Python-Skripte genutzt wird.
Wenn eine ungewöhnliche Plattform auftaucht, auf der kein nativer vorzeichenloser 8-Bit-Typ vorhanden ist, wird das Objekt sein Bestes tun, um sich auf Python-Skriptebene so darzustellen, als ob es ein Array von 8-Bit-vorzeichenlosen Werten wäre. Es ist zweifelhaft, ob viele Erweiterungen dies korrekt handhaben würden, aber Python-Skripte könnten in diesen Fällen portabel sein.
- Die Ausrichtung des allozierten Byte-Arrays ist, was die Plattformimplementierung von malloc verspricht. Ein aus einer Erweiterung erstelltes bytes-Objekt kann so bereitgestellt werden, dass es jede beliebige Ausrichtung gemäß der Entscheidung des Erweiterungsautors bietet.
Diese Ausrichtungsbeschränkung sollte es dem bytes-Objekt ermöglichen, als Speicher für alle Standard-C-Typen verwendet zu werden – einschließlich
PyComplex-Objekten oder anderen Strukturen von Standard-C-Typen. Weitere Ausrichtungsbeschränkungen können bei Bedarf von Erweiterungen bereitgestellt werden. - Das bytes-Objekt implementiert eine Teilmenge der Sequenzoperationen, die von String-/Array-Objekten bereitgestellt werden, jedoch in einigen Fällen mit leicht unterschiedlichen Semantiken. Insbesondere gibt ein Slice immer ein neues bytes-Objekt zurück, aber der zugrunde liegende Speicher wird zwischen den beiden Objekten geteilt. Diese Art von Slice-Verhalten wurde als Erstellung einer „Ansicht“ bezeichnet. Darüber hinaus sind Wiederholung und Verkettung für bytes-Objekte undefiniert und lösen eine Ausnahme aus.
Da diese Objekte wahrscheinlich in Hochleistungsanwendungen eingesetzt werden, ist ein Motiv für die Entscheidung für View-Slicing, dass das Kopieren zwischen bytes-Objekten sehr effizient sein und die Erstellung temporärer Objekte nicht erfordern sollte. Der folgende Code veranschaulicht dies
# create two 10 Meg bytes objects b1 = bytes(10000000) b2 = bytes(10000000) # copy from part of one to another with out creating a 1 Meg temporary b1[2000000:3000000] = b2[4000000:5000000]
Slice-Zuweisung, bei der die rechte Seite nicht die gleiche Länge wie die linke Seite hat, löst eine Ausnahme aus. Slice-Zuweisung funktioniert jedoch korrekt mit überlappenden Slices (typischerweise implementiert mit memmove).
- Das bytes-Objekt wird von den Modulen
pickleundcPickleals nativer Typ erkannt, um eine effiziente Serialisierung zu ermöglichen. (Tatsächlich ist dies die einzige Anforderung, die nicht durch eine Drittanbieter-Erweiterung implementiert werden kann.)Teillösungen zur Serialisierung der in einem bytes-ähnlichen Objekt gespeicherten Daten, ohne eine temporäre Kopie der Daten in einen String zu erstellen, wurden in der Vergangenheit implementiert. Die tofile- und fromfile-Methoden des array-Objekts sind gute Beispiele dafür. Das bytes-Objekt wird auch diese Methoden unterstützen. Das Pickling ist jedoch in anderen Situationen nützlich – wie im shelve-Modul oder bei der Implementierung von RPC von Python-Objekten – und es ist unerwünscht, dass der Endbenutzer zwei verschiedene Serialisierungsmechanismen verwendet, um eine effiziente Datenübertragung zu erreichen.
XXX: Werde versuchen, das Pickling des neuen bytes-Objekts so zu implementieren, dass frühere Versionen von Python es als String-Objekt entpickeln.
Beim Entpickeln wird das bytes-Objekt aus Speicher erstellt, der von Python alloziert wurde (über
malloc). Daher verliert es alle zusätzlichen Eigenschaften, die ein von einer Erweiterung bereitgestellter Zeiger möglicherweise geboten hätte (spezielle Ausrichtung oder spezielle Speichertypen).XXX: Werde versuchen, es so zu machen, dass C-Unterklassen des bytes-Typs den Speicher bereitstellen können, der entpickelt wird. Beispielsweise würde eine abgeleitete Klasse namens PageAlignedBytes zu einem seiten-ausgerichteten Speicher entpickelt werden.
Auf jeder Plattform, auf der ein int 32 Bit hat (die meisten davon), ist es derzeit unmöglich, einen String mit einer Länge zu erstellen, die größer ist als in 31 Bit darstellbar. Daher löst das Pickling zu einem String eine Ausnahme aus, wenn die Operation nicht möglich ist.
Zumindest auf Plattformen, die große Dateien unterstützen (viele davon), sollte das Pickling großer bytes-Objekte in Dateien über wiederholte Aufrufe der Methode
file.write()möglich sein. - Der bytes-Typ unterstützt die Schnittstelle
PyBufferProcs, aber ein bytes-Objekt gibt die zusätzliche Garantie, dass der Zeiger nicht freigegeben oder neu alloziert wird, solange eine Referenz auf das bytes-Objekt gehalten wird. Dies impliziert, dass ein bytes-Objekt nach der Erstellung nicht größenveränderbar ist, aber die globale Interpreter-Sperre (GIL) freigegeben werden kann, während ein separater Thread den Zeiger manipuliert, wenn der TestPyBytes_Check(...)erfolgreich ist.Diese Eigenschaft des bytes-Objekts ermöglicht seinen Einsatz in Situationen wie asynchroner Datei-E/A oder auf Mehrprozessormaschinen, bei denen der Zeiger, der von
PyBufferProcserhalten wird, unabhängig von der globalen Interpreter-Sperre verwendet wird.Zu wissen, dass der Zeiger nach der Freigabe der GIL nicht neu alloziert oder freigegeben werden kann, gibt Erweiterungsautoren die Möglichkeit, echte Nebenläufigkeit zu erreichen und zusätzliche Prozessoren für langwierige Berechnungen auf dem Zeiger zu nutzen.
- In C/C++-Erweiterungen kann das bytes-Objekt aus einem bereitgestellten Zeiger und einer Destruktorfunktion erstellt werden, um den Speicher freizugeben, wenn die Referenzanzahl auf null fällt.
Die spezielle Implementierung des Slicings für das bytes-Objekt ermöglicht es mehreren bytes-Objekten, auf denselben Zeiger/Destruktor zu verweisen. Daher wird eine Referenzzählung für das tatsächliche Zeiger/Destruktor geführt. Diese Referenzzählung ist getrennt von der Referenzzählung, die typischerweise mit Python-Objekten verbunden ist.
XXX: Es könnte wünschenswert sein, das interne referenzgezählte Objekt als tatsächliches Python-Objekt freizulegen. Wenn ein guter Anwendungsfall auftritt, sollte es möglich sein, dies später ohne Verlust der Abwärtskompatibilität zu implementieren.
- Es ist auch möglich, das bytes-Objekt als schreibgeschützt zu kennzeichnen. In diesem Fall ist es nicht wirklich veränderbar, bietet aber die anderen Funktionen eines bytes-Objekts.
- Das bytes-Objekt verfolgt die Länge seiner Daten mit einem Python
LONG_LONG-Typ. Obwohl die aktuelle Definition fürPyBufferProcsdie Länge auf die Größe eines int beschränkt, schlägt diese PEP keine Änderungen dort vor. Stattdessen können Erweiterungen diese Grenze umgehen, indem sie einen explizitenPyBytes_Check(...)-Aufruf tätigen und, wenn dieser erfolgreich ist, einenPyBytes_GetReadBuffer(...)oderPyBytes_GetWriteBuffer-Aufruf tätigen, um den Zeiger und die volle Länge des Objekts alsLONG_LONGzu erhalten.Das bytes-Objekt löst eine Ausnahme aus, wenn der Standardmechanismus
PyBufferProcsverwendet wird und die Größe des bytes-Objekts größer ist als ein Integer darstellen kann.Aus Python-Skripten ist das bytes-Objekt mit Longs abfragbar, sodass die 32-Bit-Integer-Grenze vermieden werden kann.
Es gibt immer noch ein Problem mit der Funktion
len(), da siePyObject_Size()ist und diese ebenfalls einen int zurückgibt. Als Workaround stellt das bytes-Objekt eine Methode.length()bereit, die einen Long zurückgibt. - Das bytes-Objekt kann auf Python-Skriptebene konstruiert werden, indem eine ganze Zahl/ein Long an den bytes-Konstruktor übergeben wird, um die Anzahl der zu allozierenden Bytes anzugeben. Zum Beispiel
b = bytes(100000) # alloc 100K bytes
Der Konstruktor kann auch ein anderes bytes-Objekt entgegennehmen. Dies ist nützlich für die Implementierung von Unpickling und für die Konvertierung eines Lese-/Schreib-bytes-Objekts in ein schreibgeschütztes. Ein optionales zweites Argument wird verwendet, um die Erstellung eines schreibgeschützten bytes-Objekts zu kennzeichnen.
- Aus der C-API kann das bytes-Objekt mit einer der folgenden Signaturen alloziert werden
PyObject* PyBytes_FromLength(LONG_LONG len, int readonly); PyObject* PyBytes_FromPointer(void* ptr, LONG_LONG len, int readonly void (*dest)(void *ptr, void *user), void* user);
In der Funktion
PyBytes_FromPointer(...)wird, wenn der Destruktorfunktionszeiger alsNULLübergeben wird, dieser nicht aufgerufen. Dies sollte nur für die Erstellung von bytes-Objekten aus statisch alloziertem Speicher verwendet werden.Der Benutzerzeiger wurde an anderer Stelle als Closure bezeichnet. Es ist ein Zeiger, den der Benutzer für beliebige Zwecke verwenden kann. Er wird bei der Bereinigung an die Destruktorfunktion übergeben und kann für eine Reihe von Dingen nützlich sein. Wenn der Benutzerzeiger nicht benötigt wird, sollte
NULLstattdessen übergeben werden. - Der bytes-Typ wird eine new-style-Klasse sein, da dies scheint, wohin alle Standard-Python-Typen tendieren.
Kontrast zu bestehenden Typen
Die gebräuchlichste Methode, um den Mangel an einem bytes-Objekt zu umgehen, war die einfache Verwendung eines String-Objekts an seiner Stelle. Binärdateien, die struct/array-Module und mehrere andere Beispiele existieren dafür. Abgesehen vom stilistischen Problem, dass diese Verwendungen typischerweise nichts mit Text-Strings zu tun haben, gibt es das reale Problem, dass Strings nicht veränderbar sind, sodass eine direkte Manipulation der in diesen Fällen zurückgegebenen Daten nicht möglich ist. Außerdem bedeuten zahlreiche Optimierungen im String-Modul (wie das Caching des Hash-Werts oder das Interning der Zeiger), dass Erweiterungsautoren auf sehr dünnem Eis wandeln, wenn sie versuchen, die Regeln mit dem String-Objekt zu brechen.
Das buffer-Objekt schien dazu bestimmt zu sein, den Zweck zu erfüllen, den das bytes-Objekt erfüllen soll, aber mehrere Mängel in seiner Implementierung [1] haben es in vielen gängigen Fällen weniger nützlich gemacht. Das buffer-Objekt traf eine andere Wahl für sein Slice-Verhalten (es gibt neue Strings anstelle von Puffern für Slicing und andere Operationen zurück) und es macht nicht viele der Versprechungen bezüglich Ausrichtung oder der Möglichkeit, die GIL freizugeben, die das bytes-Objekt macht.
Auch in Bezug auf das buffer-Objekt ist es nicht möglich, das buffer-Objekt einfach durch das bytes-Objekt zu ersetzen und die Abwärtskompatibilität zu wahren. Das buffer-Objekt bietet einen Mechanismus, um den von PyBufferProcs bereitgestellten Zeiger eines anderen Objekts zu nehmen und ihn als seinen eigenen darzustellen. Da das Verhalten des anderen Objekts nicht garantiert werden kann, denselben strengen Regeln zu folgen, denen ein bytes-Objekt folgt, kann es nicht an Orten verwendet werden, an denen ein bytes-Objekt verwendet werden könnte.
Das array-Modul unterstützt die Erstellung eines Arrays von Bytes, bietet aber keine C-API zum Bereitstellen von Zeigern und Destruktoren für von Erweiterungen bereitgestellten Speicher. Dies macht es unbrauchbar für die Konstruktion von Objekten aus Shared Memory oder Speicher mit spezieller Ausrichtung oder Sperrung für Dinge wie DMA-Transfers. Außerdem pickelt das array-Objekt derzeit nicht. Da das array-Objekt schließlich seine Inhalte über die extend-Methode wachsen lässt, kann der Zeiger geändert werden, wenn die GIL während seiner Verwendung nicht gehalten wird.
Das Erstellen eines buffer-Objekts aus einem array-Objekt hat dasselbe Problem, einen ungültigen Zeiger zu hinterlassen, wenn das array-Objekt neu dimensioniert wird.
Das mmap-Objekt bedient seine spezielle Nische, versucht aber nicht, eine breitere Klasse von Problemen zu lösen.
Schließlich kann keine Drittanbieter-Erweiterung das Pickling implementieren, ohne ein temporäres Objekt eines Standard-Python-Typs zu erstellen. Zum Beispiel ist es in der Numeric-Community unangenehm, dass ein großes Array nicht pickeln kann, ohne einen großen Binärstring zu erstellen, um die Array-Daten zu duplizieren.
Abwärtskompatibilität
Die einzige Möglichkeit für Probleme mit der Abwärtskompatibilität, die dem Autor bekannt sind, sind frühere Python-Versionen, die versuchen, Daten mit dem neuen bytes-Typ zu entpickeln.
Referenzimplementierung
XXX: Die tatsächliche Implementierung ist im Gange, aber Änderungen sind weiterhin möglich, da diese PEP weiter geprüft wird.
Die folgenden neuen Dateien werden zur Python-Basis hinzugefügt
Include/bytesobject.h # C interface
Objects/bytesobject.c # C implementation
Lib/test/test_bytes.py # unit testing
Doc/lib/libbytes.tex # documentation
Die folgenden Dateien werden ebenfalls geändert
Include/Python.h # adding bytesmodule.h include file
Python/bltinmodule.c # adding the bytes type object
Modules/cPickle.c # adding bytes to the standard types
Lib/pickle.py # adding bytes to the standard types
Es ist möglich, dass mehrere andere Module bereinigt und im Hinblick auf das bytes-Objekt implementiert werden könnten. Das mmap-Modul kommt einem zuerst in den Sinn, aber wie oben erwähnt, wäre es möglich, das array-Modul als reines Python-Modul neu zu implementieren. Obwohl es attraktiv ist, dass diese PEP tatsächlich die Menge des Quellcodes um einen gewissen Betrag reduzieren könnte, ist der Autor der Meinung, dass dies ein unnötiges Risiko für den Bruch bestehender Anwendungen darstellen könnte und derzeit vermieden werden sollte.
Zusätzliche Hinweise/Kommentare
- Guido van Rossum fragte, ob es sinnvoll wäre, ein bytes-Objekt aus einem mmap-Objekt erstellen zu können. Das mmap-Objekt scheint die notwendigen Anforderungen zu unterstützen, um Speicher für ein bytes-Objekt bereitzustellen. (Es wird nicht größenverändert, und der Zeiger ist für die Lebensdauer des Objekts gültig.) Daher könnte eine Methode zum mmap-Modul hinzugefügt werden, damit ein bytes-Objekt direkt aus einem mmap-Objekt erstellt werden kann. Ein erster Versuch, wie dies implementiert werden könnte, wäre die Verwendung der oben beschriebenen Funktion
PyBytes_FromPointer()und die Übergabe desmmap_objectals Benutzerzeiger. Die Destruktorfunktion würde dasmmap_objectfür die Bereinigung decrefieren. - Todd Miller merkt an, dass es nützlich sein könnte, zwei neue Funktionen zu haben:
PyObject_AsLargeReadBuffer()undPyObject_AsLargeWriteBuffer, diePyObject_AsReadBuffer()undPyObject_AsWriteBuffer()ähneln, aber die Möglichkeit unterstützen, eineLONG_LONG-Länge zusätzlich zumvoid*-Zeiger zu erhalten. Diese Funktionen würden es Erweiterungsautoren ermöglichen, transparent mit bytes-Objekten (dieLONG_LONG-Längen unterstützen) und den meisten anderen Puffer-ähnlichen Objekten (die nur int-Längen unterstützen) zu arbeiten. Diese Funktionen könnten anstelle von oder zusätzlich zur Erstellung spezifischer FunktionenPyByte_GetReadBuffer()undPyBytes_GetWriteBuffer()stehen.XXX: Der Autor hält dies für eine sehr gute Idee, da es den Weg für andere Objekte ebnet, schließlich große (64-Bit) Zeiger zu unterstützen, und es sollte nur abstract.c und abstract.h beeinflussen. Sollte dies oben hinzugefügt werden?
- Es wurde allgemein vereinbart, dass die missbräuchliche Verwendung der Segmentanzahl der
PyBufferProcs-Schnittstelle kein guter Hack ist, um die 31-Bit-Beschränkung der Länge zu umgehen. Wenn Sie nicht wissen, was das bedeutet, sind Sie in guter Gesellschaft. Die meisten Codes in der Python-Basis und vermutlich in vielen Drittanbieter-Erweiterungen geben auf, wenn die Segmentanzahl nicht 1 ist.
Referenzen
Urheberrecht
Dieses Dokument wurde gemeinfrei erklärt.
Quelle: https://github.com/python/peps/blob/main/peps/pep-0296.rst
Zuletzt geändert: 2025-02-01 08:55:40 GMT