PEP 307 – Erweiterungen des Pickle-Protokolls
- Autor:
- Guido van Rossum, Tim Peters
- Status:
- Final
- Typ:
- Standards Track
- Erstellt:
- 31. Jan 2003
- Python-Version:
- 2.3
- Post-History:
- 07. Feb 2003
Inhaltsverzeichnis
- Einleitung
- Motivation
- Protokollversionen
- Sicherheitsaspekte
- Erweiterte
__reduce__API - Die
__reduce_ex__API - Anpassung des Picklings ohne
__reduce__Implementierung - Die
__newobj__Entpicklungsfunktion - Die Erweiterungsregistry
- Das
copyModul - Pickling von Python-Longs
- Pickling von Bools
- Pickling von kleinen Tupeln
- Protokollidentifikation
- Pickling von großen Listen und Dictionaries
- Urheberrecht
Einleitung
Das Pickling von neuen Klasseninstanzen in Python 2.2 erfolgt etwas umständlich und führt zu einer erheblichen Vergrößerung der Pickle-Dateien im Vergleich zu klassischen Klasseninstanzen. Dieses PEP dokumentiert ein neues Pickle-Protokoll in Python 2.3, das dieses und viele andere Pickle-Probleme adressiert.
Es gibt zwei Seiten bei der Spezifikation eines neuen Pickle-Protokolls: Der Byte-Stream, der die gepickelten Daten konstituiert, muss spezifiziert werden, und die Schnittstelle zwischen Objekten und den Pickling- und Entpicklungs-Engines muss spezifiziert werden. Dieses PEP konzentriert sich auf API-Themen, obwohl es gelegentlich auf Details des Byte-Stream-Formats eingehen kann, um eine Wahl zu motivieren. Das Pickle-Byte-Stream-Format wird formal durch das Standardbibliotheksmodul pickletools.py dokumentiert (bereits in CVS für Python 2.3 eingecheckt).
Dieses PEP versucht, die Schnittstelle zwischen gepickelten Objekten und dem Pickling-Prozess vollständig zu dokumentieren, wobei Neuerungen durch die Angabe "neu in diesem PEP" hervorgehoben werden. (Die Schnittstelle zum Aufrufen von Pickling oder Entpickling wird nicht vollständig abgedeckt, außer den Änderungen an der API zur Angabe des zu verwendenden Pickle-Protokolls für Pickler.)
Motivation
Das Pickling von neuen Klasseninstanzen führt zu einer erheblichen Vergrößerung der Pickle-Dateien. Zum Beispiel:
class C(object): # Omit "(object)" for classic class
pass
x = C()
x.foo = 42
print len(pickle.dumps(x, 1))
Der binäre Pickle für das klassische Objekt beanspruchte 33 Bytes, und für das neue Klassenobjekt 86 Bytes.
Die Gründe für die Vergrößerung sind komplex, werden aber meist dadurch verursacht, dass neue Klasseninstanzen __reduce__ verwenden, um überhaupt pickelbar zu sein. Nach reiflicher Überlegung sind wir zu dem Schluss gekommen, dass der einzige Weg, die Pickle-Größen für neue Klasseninstanzen zu reduzieren, darin besteht, neue Opcodes zum Pickle-Protokoll hinzuzufügen. Das Endergebnis ist, dass mit dem neuen Protokoll die Pickle-Größe im obigen Beispiel 35 Bytes beträgt (zwei zusätzliche Bytes werden am Anfang verwendet, um die Protokollversion anzuzeigen, obwohl dies nicht unbedingt notwendig ist).
Protokollversionen
Zuvor unterschied das Pickling (aber nicht das Entpickeln) zwischen Text- und Binärmodus. Per Design ist der Binärmodus eine Obermenge des Textmodus, und Entpickler müssen nicht im Voraus wissen, ob ein eingehender Pickle Text- oder Binärmodus verwendet. Die für das Entpickeln verwendete virtuelle Maschine ist unabhängig vom Modus dieselbe; bestimmte Opcodes werden im Textmodus einfach nicht verwendet.
Retroaktiv wird der Textmodus nun Protokoll 0 genannt und der Binärmodus Protokoll 1. Das neue Protokoll wird Protokoll 2 genannt. Nach der Tradition der Pickle-Protokolle ist Protokoll 2 eine Obermenge von Protokoll 1. Aber nur damit zukünftige Pickle-Protokolle nicht gezwungen sind, Obermengen der ältesten Protokolle zu sein, wird am Anfang eines Protokoll-2-Pickles ein neuer Opcode eingefügt, der anzeigt, dass Protokoll 2 verwendet wird. Bis heute konnte jede Python-Version Pickles lesen, die von allen früheren Versionen geschrieben wurden. Natürlich können unter Protokoll *N* geschriebene Pickles nicht von Versionen von Python gelesen werden, die älter sind als die, die Protokoll *N* eingeführt haben.
Mehrere Funktionen, Methoden und Konstruktoren, die zum Pickling verwendet werden, nahmen früher ein Positionsargument namens 'bin' entgegen, das ein Flag war und standardmäßig auf 0 gesetzt war, was den Binärmodus anzeigte. Dieses Argument wurde in 'protocol' umbenannt und gibt nun die Protokollnummer an, wobei es immer noch standardmäßig auf 0 gesetzt ist.
Es kommt vor, dass die Übergabe von 2 als 'bin'-Argument in früheren Python-Versionen denselben Effekt hatte wie die Übergabe von 1. Dennoch wird hier ein Sonderfall hinzugefügt: Die Übergabe einer negativen Zahl wählt die höchste von einer bestimmten Implementierung unterstützte Protokollversion. Dies funktioniert auch in früheren Python-Versionen und kann daher verwendet werden, um das höchste verfügbare Protokoll auf eine Weise auszuwählen, die sowohl rückwärts- als auch zukunftskompatibel ist. Zusätzlich wird eine neue Modulkonstante HIGHEST_PROTOCOL sowohl von pickle als auch von cPickle bereitgestellt, die der höchsten von dem Modul lesbaren Protokollnummer entspricht. Dies ist sauberer als -1 zu übergeben, kann aber vor Python 2.3 nicht verwendet werden.
Das Modul pickle.py hat die Übergabe des 'bin'-Wertes als Schlüsselwortargument anstelle eines Positionsarguments unterstützt. (Dies wird nicht empfohlen, da cPickle nur Positionsargumente akzeptiert, aber es funktioniert...) Die Übergabe von 'bin' als Schlüsselwortargument ist veraltet, und in diesem Fall wird eine PendingDeprecationWarning ausgegeben. Sie müssen den Python-Interpreter mit -Wa oder einer Variation davon aufrufen, um PendingDeprecationWarning-Meldungen zu sehen. In Python 2.4 kann die Warnungsklasse zu DeprecationWarning aufgewertet werden.
Sicherheitsaspekte
In früheren Versionen von Python führte das Entpickeln eine "Sicherheitsprüfung" für bestimmte Operationen durch und weigerte sich, Funktionen oder Konstruktoren aufzurufen, die nicht als "sicher für das Entpickeln" markiert waren, entweder durch ein Attribut __safe_for_unpickling__, das auf 1 gesetzt war, oder durch Registrierung in einer globalen Registry, copy_reg.safe_constructors.
Dieses Feature vermittelt ein falsches Sicherheitsgefühl: Niemand hat jemals die notwendige, umfangreiche Code-Prüfung durchgeführt, um zu beweisen, dass das Entpickeln unzuverlässiger Pickles keinen unerwünschten Code aufrufen kann, und tatsächlich machen Fehler im Python 2.2 pickle.py Modul es einfach, diese Sicherheitsmaßnahmen zu umgehen.
Wir sind der festen Überzeugung, dass es im Internet besser ist zu wissen, dass man ein unsicheres Protokoll verwendet, als einem Protokoll zu vertrauen, das sicher sein soll, dessen Implementierung aber nicht gründlich geprüft wurde. Selbst hochwertige Implementierungen weit verbreiteter Protokolle weisen regelmäßig Mängel auf; die Pickle-Implementierung von Python kann solche Garantien ohne einen wesentlich größeren Zeitaufwand einfach nicht geben. Daher werden ab Python 2.3 alle Sicherheitsprüfungen beim Entpickeln offiziell entfernt und durch diese Warnung ersetzt:
Warnung
Pickeln Sie keine Daten, die von einer nicht vertrauenswürdigen oder nicht authentifizierten Quelle stammen.
Dieselbe Warnung gilt auch für frühere Python-Versionen, trotz der dort vorhandenen Sicherheitsprüfungen.
Erweiterte __reduce__ API
Es gibt mehrere APIs, die eine Klasse zur Steuerung des Pickling verwenden kann. Vielleicht die beliebtesten davon sind __getstate__ und __setstate__; aber die mächtigste ist __reduce__. (Es gibt auch __getinitargs__, und wir fügen __getnewargs__ weiter unten hinzu.)
Es gibt mehrere Möglichkeiten, __reduce__-Funktionalität bereitzustellen: Eine Klasse kann eine __reduce__-Methode oder eine __reduce_ex__-Methode (siehe nächster Abschnitt) implementieren, oder eine Reduce-Funktion kann in copy_reg deklariert werden (copy_reg.dispatch_table ordnet Klassen Funktionen zu). Die Rückgabewerte werden jedoch genau gleich interpretiert, und wir werden sie kollektiv als __reduce__ bezeichnen.
Wichtig: Das Pickling von Instanzen klassischer Klassen sucht nicht nach einer __reduce__ oder __reduce_ex__ Methode oder einer Reduce-Funktion in der copy_reg Dispatch-Tabelle, so dass eine klassische Klasse __reduce__ Funktionalität in dem hier gemeinten Sinne nicht bereitstellen kann. Eine klassische Klasse muss __getinitargs__ und/oder __getstate__ verwenden, um das Pickling anzupassen. Diese werden unten beschrieben.
__reduce__ muss entweder einen String oder ein Tupel zurückgeben. Wenn es einen String zurückgibt, handelt es sich um ein Objekt, dessen Zustand nicht gepickelt werden soll, sondern stattdessen eine Referenz auf ein äquivalentes Objekt, auf das per Name verwiesen wird. Überraschenderweise sollte der von __reduce__ zurückgegebene String der lokale Name des Objekts (relativ zu seinem Modul) sein; das pickle Modul durchsucht den Modul-Namespace, um das Modul des Objekts zu ermitteln.
Der Rest dieses Abschnitts befasst sich mit dem von __reduce__ zurückgegebenen Tupel. Es ist ein Tupel variabler Länge, von 2 bis 5 Elementen. Die ersten beiden Elemente (Funktion und Argumente) sind obligatorisch. Die restlichen Elemente sind optional und können vom Ende weggelassen werden; die Übergabe von None für den Wert eines optionalen Elements wirkt sich genauso aus wie das Weglassen. Die letzten beiden Elemente sind neu in diesem PEP. Die Elemente sind in der Reihenfolge:
| function | Obligatorisch. Ein aufrufbares Objekt (nicht unbedingt eine Funktion), das zum Erstellen der initialen Version des Objekts verwendet wird; dem Objekt können später Zustandsinformationen hinzugefügt werden, um den gepickelten Zustand vollständig zu rekonstruieren. Diese Funktion muss selbst pickelbar sein. Siehe den Abschnitt über |
| Argumente | Obligatorisch. Ein Tupel, das die Argumentliste für die Funktion angibt. Als Sonderfall, der für |
Beim Entpickeln wird function(*arguments) aufgerufen, um ein initiales Objekt zu erstellen, das unten als *obj* bezeichnet wird. Wenn die restlichen Elemente weggelassen werden, ist das Entpickeln für dieses Objekt beendet und *obj* ist das Ergebnis. Andernfalls wird *obj* beim Entpickeln durch jedes angegebene Element wie folgt modifiziert.
| Zustand | Optional. Zusätzlicher Zustand. Wenn dies nicht obj.__dict__.update(state)
oder, wenn der for k, v in state.items():
setattr(obj, k, v)
|
| Listen-Elemente | Optional, und neu in diesem PEP. Wenn dies nicht |
| Dict-Elemente | Optional, und neu in diesem PEP. Wenn dies nicht |
Hinweis: In Python 2.2 und früher wurde bei Verwendung von cPickle der Zustand gepickelt, wenn er vorhanden war, auch wenn er None war; der einzige sichere Weg, den Aufruf von __setstate__ zu vermeiden, war die Rückgabe eines Zwei-Tupels von __reduce__. (Aber pickle.py pickelte den Zustand nicht, wenn er None war.) In Python 2.3 wird __setstate__ beim Entpickeln nie aufgerufen, wenn __reduce__ einen Zustand mit dem Wert None beim Pickling zurückgibt.
Eine __reduce__ Implementierung, die sowohl unter Python 2.2 als auch unter Python 2.3 funktionieren muss, kann die Variable pickle.format_version überprüfen, um festzustellen, ob die Features für Listen- und Dict-Elemente verwendet werden sollen. Wenn dieser Wert >= "2.0" ist, werden sie unterstützt. Wenn nicht, sollten alle Listen- oder Dict-Elemente irgendwie im 'state'-Rückgabewert enthalten sein, und die __setstate__ Methode sollte darauf vorbereitet sein, Listen- oder Dict-Elemente als Teil des Zustands zu akzeptieren (wie dies geschieht, liegt in der Verantwortung der Anwendung).
Die __reduce_ex__ API
Es ist manchmal nützlich, die Protokollversion zu kennen, wenn __reduce__ implementiert wird. Dies kann durch die Implementierung einer Methode namens __reduce_ex__ anstelle von __reduce__ geschehen. __reduce_ex__ wird, wenn sie existiert, vor __reduce__ aufgerufen (Sie können __reduce__ immer noch für Abwärtskompatibilität bereitstellen). Die __reduce_ex__ Methode wird mit einem einzigen ganzzahligen Argument aufgerufen, der Protokollversion.
Die 'object'-Klasse implementiert sowohl __reduce__ als auch __reduce_ex__; wenn jedoch eine Unterklasse __reduce__ überschreibt, aber nicht __reduce_ex__, erkennt die __reduce_ex__ Implementierung dies und ruft __reduce__ auf.
Anpassung des Picklings ohne __reduce__ Implementierung
Wenn keine __reduce__ Implementierung für eine bestimmte Klasse verfügbar ist, müssen drei Fälle separat betrachtet werden, da sie unterschiedlich behandelt werden:
- Instanzen klassischer Klassen, alle Protokolle
- Instanzen neuer Klassen, Protokolle 0 und 1
- Instanzen neuer Klassen, Protokoll 2
In C implementierte Typen werden als neue Klassen betrachtet. Mit Ausnahme der gängigen eingebauten Typen müssen diese jedoch eine __reduce__-Implementierung bereitstellen, um mit Protokoll 0 oder 1 pickelbar zu sein. Protokoll 2 unterstützt auch eingebaute Typen, die __getnewargs__, __getstate__ und __setstate__ bereitstellen.
Fall 1: Pickling von Instanzen klassischer Klassen
Dieser Fall ist für alle Protokolle unverändert und entspricht Python 2.1.
Bei klassischen Klassen wird __reduce__ nicht verwendet. Stattdessen können klassische Klassen ihr Pickling durch die Bereitstellung von Methoden namens __getstate__, __setstate__ und __getinitargs__ anpassen. Fehlen diese, wird eine Standard-Pickling-Strategie für klassische Klasseninstanzen implementiert, die funktioniert, solange alle Instanzvariablen pickelbar sind. Diese Standardstrategie wird in Form von Standardimplementierungen von __getstate__ und __setstate__ dokumentiert.
Die primären Möglichkeiten zur Anpassung des Pickling von klassischen Klasseninstanzen sind die Bereitstellung von __getstate__ und/oder __setstate__ Methoden. Es ist in Ordnung, wenn eine Klasse eine dieser Methoden implementiert, aber nicht die andere, solange sie mit der Standardversion kompatibel ist.
Die __getstate__ Methode
Die __getstate__ Methode sollte einen pickelbaren Wert zurückgeben, der den Zustand des Objekts repräsentiert, ohne auf das Objekt selbst zu verweisen. Wenn keine __getstate__ Methode existiert, wird eine Standardimplementierung verwendet, die self.__dict__ zurückgibt.
Die __setstate__ Methode
Die __setstate__ Methode sollte ein Argument entgegennehmen; sie wird mit dem von __getstate__ (oder seiner Standardimplementierung) zurückgegebenen Wert aufgerufen.
Wenn keine __setstate__ Methode existiert, wird eine Standardimplementierung bereitgestellt, die davon ausgeht, dass der Zustand ein Dictionary ist, das Instanzvariablennamen Werten zuordnet. Die Standardimplementierung versucht zwei Dinge:
- Zuerst versucht sie,
self.__dict__.update(state)aufzurufen. - Wenn der
update()-Aufruf mit einerRuntimeErrorAusnahme fehlschlägt, ruft siesetattr(self, key, value)für jedes(key, value)-Paar im Zustandsdictionary auf. Dies geschieht nur beim Entpickeln im eingeschränkten Ausführungsmodus (siehe das Modulrexecder Standardbibliothek).
Die __getinitargs__ Methode
Die __setstate__ Methode (oder ihre Standardimplementierung) erfordert, dass ein neues Objekt bereits existiert, damit seine __setstate__ Methode aufgerufen werden kann. Der Punkt ist, ein neues Objekt zu erstellen, das nicht vollständig initialisiert ist; insbesondere sollte die __init__-Methode der Klasse, wenn möglich, nicht aufgerufen werden.
Dies sind die Möglichkeiten:
- Normalerweise wird der folgende Trick verwendet: Erstellen Sie eine Instanz einer trivialen klassischen Klasse (eine ohne Methoden oder Instanzvariablen) und verwenden Sie dann die
__class__Zuweisung, um ihre Klasse auf die gewünschte Klasse zu ändern. Dies erstellt eine Instanz der gewünschten Klasse mit einem leeren__dict__, deren__init__nicht aufgerufen wurde. - Wenn die Klasse jedoch eine Methode namens
__getinitargs__hat, wird der obige Trick nicht verwendet, und eine Klasseninstanz wird erstellt, indem das von__getinitargs__zurückgegebene Tupel als Argumentenliste für den Klassenkonstruktor verwendet wird. Dies geschieht auch, wenn__getinitargs__ein leeres Tupel zurückgibt – eine__getinitargs__Methode, die()zurückgibt, ist nicht äquivalent zum Fehlen einer__getinitargs__Methode überhaupt.__getinitargs__*muss* ein Tupel zurückgeben. - Im eingeschränkten Ausführungsmodus funktioniert der Trick aus dem ersten Punkt nicht; in diesem Fall wird der Klassenkonstruktor mit einer leeren Argumentenliste aufgerufen, wenn keine
__getinitargs__Methode vorhanden ist. Das bedeutet, dass eine klassische Klasse, um im eingeschränkten Ausführungsmodus entpickelbar zu sein, entweder__getinitargs__implementieren muss oder ihr Konstruktor (d.h. ihre__init__-Methode) ohne Argumente aufrufbar sein muss.
Fall 2: Pickling von Instanzen neuer Klassen mit Protokoll 0 oder 1
Dieser Fall ist unverändert gegenüber Python 2.2. Für besseres Pickling von neuen Klasseninstanzen, wenn Rückwärtskompatibilität keine Rolle spielt, sollte Protokoll 2 verwendet werden; siehe Fall 3 unten.
Neue Klassen, ob in C oder Python implementiert, erben eine Standard-__reduce__ Implementierung von der universellen Basisklasse 'object'.
Diese Standard-__reduce__ Implementierung wird nicht für diejenigen eingebauten Typen verwendet, für die das pickle-Modul eine eingebaute Unterstützung hat. Hier ist eine vollständige Liste dieser Typen:
- Konkrete eingebaute Typen:
NoneType,bool,int,float,complex,str,unicode,tuple,list,dict. (Complex wird durch eine incopy_regregistrierte__reduce__Implementierung unterstützt.) In Jython istPyStringMapebenfalls in dieser Liste enthalten. - Klassische Instanzen.
- Klassische Klassenobjekte, Python-Funktionsobjekte, eingebaute Funktions- und Methodenobjekte sowie neue Typenobjekte (== neue Klassenobjekte). Diese werden per Name und nicht per Wert gepickelt: Beim Entpickeln wird eine Referenz auf ein Objekt mit demselben Namen (der vollständig qualifizierte Modulname plus der Variablenname in diesem Modul) eingesetzt.
Die Standard-__reduce__ Implementierung schlägt beim Pickling für eingebaute Typen, die nicht oben genannt sind, und für neue Klassen, die in C implementiert sind, fehl: Wenn sie pickelbar sein sollen, müssen sie eine benutzerdefinierte __reduce__ Implementierung unter den Protokollen 0 und 1 bereitstellen.
Für neue Klassen, die in Python implementiert sind, funktioniert die Standard-__reduce__ Implementierung (copy_reg._reduce) wie folgt:
Sei D die Klasse des zu pickelnden Objekts. Finden Sie zuerst die nächstgelegene Basisklasse, die in C implementiert ist (entweder als eingebauter Typ oder als von einer Erweiterungsklasse definierter Typ). Nennen Sie diese Basisklasse B und die Klasse des zu pickelnden Objekts D. Sofern B nicht die Klasse 'object' ist, müssen Instanzen der Klasse B pickelbar sein, entweder durch eingebaute Unterstützung (wie in den obigen drei Aufzählungspunkten definiert) oder durch eine nicht standardmäßige __reduce__ Implementierung. B darf nicht dieselbe Klasse wie D sein (wäre dies der Fall, würde dies bedeuten, dass D nicht in Python implementiert ist).
Das von der Standard-__reduce__ erzeugte aufrufbare Objekt ist copy_reg._reconstructor, und sein Argument-Tupel ist (D, B, basestate), wobei basestate None ist, wenn B die eingebaute object-Klasse ist, und basestate ist:
basestate = B(obj)
wenn B nicht die eingebaute object-Klasse ist. Dies ist auf das Pickling von Unterklassen eingebauter Typen ausgerichtet, bei denen beispielsweise list(some_list_subclass_instance) "den Listenanteil" der list-Unterklasseninstanz ergibt.
Das Objekt wird beim Entpickeln von copy_reg._reconstructor wie folgt neu erstellt:
obj = B.__new__(D, basestate)
B.__init__(obj, basestate)
Objekte, die die Standardimplementierung von __reduce__ verwenden, können diese anpassen, indem sie die Methoden __getstate__ und/oder __setstate__ definieren. Diese funktionieren fast genauso wie oben für klassische Klassen beschrieben, mit der Ausnahme, dass, wenn __getstate__ ein Objekt (beliebigen Typs) zurückgibt, dessen Wert als falsch betrachtet wird (z. B. None, eine Zahl mit dem Wert Null oder eine leere Sequenz oder Zuordnung), dieser Zustand nicht gepickelt wird und __setstate__ überhaupt nicht aufgerufen wird. Wenn __getstate__ existiert und einen wahren Wert zurückgibt, wird dieser Wert das dritte Element des Tupels, das von der Standardimplementierung von __reduce__ zurückgegeben wird, und zur Zeit des Unpickelns wird der Wert an __setstate__ übergeben. Wenn __getstate__ nicht existiert, aber obj.__dict__ existiert, dann wird obj.__dict__ das dritte Element des von __reduce__ zurückgegebenen Tupels, und wiederum zur Zeit des Unpickelns wird der Wert an obj.__setstate__ übergeben. Die Standardimplementierung von __setstate__ ist die gleiche wie für klassische Klassen, wie oben beschrieben.
Beachten Sie, dass diese Strategie Slots ignoriert. Instanzen von New-Style-Klassen, die Slots, aber keine __getstate__-Methode haben, können mit den Protokollen 0 und 1 nicht gepickelt werden; der Code prüft explizit auf diese Bedingung.
Beachten Sie, dass beim Pickeln von New-Style-Klasseninstanzen __getinitargs__, falls vorhanden (und unter allen Protokollen), ignoriert wird. __getinitargs__ ist nur für klassische Klassen nützlich.
Fall 3: Pickling von Instanzen neuer Klassen mit Protokoll 2
Unter Protokoll 2 wird die Standardimplementierung von __reduce__, die von der Basisklasse „object“ geerbt wird, *ignoriert*. Stattdessen wird eine andere Standardimplementierung verwendet, die ein effizienteres Pickeln von New-Style-Klasseninstanzen ermöglicht als mit den Protokollen 0 oder 1 möglich, auf Kosten der Rückwärtskompatibilität mit Python 2.2 (d. h. nicht mehr als dass ein Protokoll 2 Pickle nicht vor Python 2.3 entpickelt werden kann).
Die Anpassung verwendet drei spezielle Methoden: __getstate__, __setstate__ und __getnewargs__ (beachten Sie, dass __getinitargs__ wieder ignoriert wird). Es ist in Ordnung, wenn eine Klasse eine oder mehrere, aber nicht alle dieser Methoden implementiert, solange sie mit den Standardimplementierungen kompatibel ist.
Die __getstate__ Methode
Die Methode __getstate__ sollte einen pickelbaren Wert zurückgeben, der den Zustand des Objekts darstellt, ohne sich auf das Objekt selbst zu beziehen. Wenn keine __getstate__-Methode existiert, wird eine Standardimplementierung verwendet, die unten beschrieben wird.
Es gibt einen subtilen Unterschied zwischen klassischen und neuen Klassen hier: wenn die __getstate__-Methode einer klassischen Klasse None zurückgibt, wird self.__setstate__(None) als Teil des Unpickelns aufgerufen. Aber wenn die __getstate__-Methode einer neuen Klasse None zurückgibt, wird ihre __setstate__-Methode als Teil des Unpickelns überhaupt nicht aufgerufen.
Wenn keine __getstate__-Methode existiert, wird ein Standardzustand berechnet. Es gibt mehrere Fälle:
- Für eine New-Style-Klasse, die kein Instanz-
und keine__dict____slots__hat, ist der StandardzustandNone. - Für eine New-Style-Klasse, die ein Instanz-
und keine__dict____slots__hat, ist der Standardzustandself.__dict__. - Für eine New-Style-Klasse, die ein Instanz-
und__dict____slots__hat, ist der Standardzustand ein Tupel, das aus zwei Wörterbüchern besteht:self.__dict__und einem Wörterbuch, das Slot-Namen auf Slot-Werte abbildet. Nur Slots, die einen Wert haben, sind im letzteren enthalten. - Für eine New-Style-Klasse, die
__slots__und kein Instanz-hat, ist der Standardzustand ein Tupel, dessen erstes Element__dict__Noneist und dessen zweites Element ein Wörterbuch ist, das Slot-Namen auf Slot-Werte abbildet, wie im vorherigen Punkt beschrieben.
Die __setstate__ Methode
Die Methode __setstate__ sollte ein Argument entgegennehmen; sie wird mit dem von __getstate__ zurückgegebenen Wert oder mit dem oben beschriebenen Standardzustand aufgerufen, wenn keine __getstate__-Methode definiert ist.
Wenn keine __setstate__-Methode existiert, wird eine Standardimplementierung bereitgestellt, die den von der Standardimplementierung von __getstate__ zurückgegebenen Zustand verarbeiten kann, wie oben beschrieben.
Die __getnewargs__ Methode
Wie bei klassischen Klassen erfordert die Methode __setstate__ (oder ihre Standardimplementierung), dass ein neues Objekt bereits existiert, damit seine __setstate__-Methode aufgerufen werden kann.
In Protokoll 2 wird ein neuer Pickling-Opcode verwendet, der die Erstellung eines neuen Objekts wie folgt bewirkt:
obj = C.__new__(C, *args)
wobei C die Klasse des gepickelten Objekts ist und args entweder das leere Tupel ist oder das von der Methode __getnewargs__ zurückgegebene Tupel, falls definiert. __getnewargs__ muss ein Tupel zurückgeben. Das Fehlen einer __getnewargs__-Methode ist gleichbedeutend mit dem Vorhandensein einer, die () zurückgibt.
Die __newobj__ Entpicklungsfunktion
Wenn die von __reduce__ zurückgegebene Entpicklungsfunktion (das erste Element des zurückgegebenen Tupels) den Namen __newobj__ hat, geschieht bei Pickle-Protokoll 2 etwas Besonderes. Es wird angenommen, dass eine Entpicklungsfunktion mit diesem Namen die folgende Semantik hat:
def __newobj__(cls, *args):
return cls.__new__(cls, *args)
Pickle-Protokoll 2 behandelt eine Funktion mit diesem Namen speziell und gibt einen Pickling-Opcode aus, der mit 'cls' und 'args' cls.__new__(cls, *args) zurückgibt, ohne auch eine Referenz auf __newobj__ zu pickeln (dies ist derselbe Pickling-Opcode, der von Protokoll 2 für eine New-Style-Klasseninstanz verwendet wird, wenn keine __reduce__-Implementierung existiert). Dies ist der Hauptgrund, warum Protokoll 2 Pickles viel kleiner sind als klassische Pickles. Natürlich kann der Pickling-Code nicht verifizieren, dass eine Funktion namens __newobj__ tatsächlich die erwartete Semantik hat. Wenn Sie eine Entpicklungsfunktion namens __newobj__ verwenden, die etwas anderes zurückgibt, verdienen Sie, was Sie bekommen.
Es ist sicher, diese Funktion unter Python 2.2 zu verwenden; es gibt nichts in der empfohlenen Implementierung von __newobj__, das von Python 2.3 abhängt.
Die Erweiterungsregistry
Protokoll 2 unterstützt einen neuen Mechanismus zur Reduzierung der Größe von Pickles.
Wenn Klasseninstanzen (klassisch oder neu) gepickelt werden, wird der vollständige Name der Klasse (Modulname einschließlich Paketname und Klassenname) in das Pickle aufgenommen. Insbesondere für Anwendungen, die viele kleine Pickles erzeugen, ist dies ein erheblicher Overhead, der in jedem Pickle wiederholt werden muss. Für große Pickles werden bei Verwendung von Protokoll 1 wiederholte Referenzen auf denselben Klassennamen mithilfe der „Memo“-Funktion komprimiert; jeder Klassenname muss jedoch mindestens einmal pro Pickle vollständig ausgeschrieben werden, und dies verursacht einen erheblichen Overhead für kleine Pickles.
Die Erweiterungsregistrierung ermöglicht es, die am häufigsten verwendeten Namen durch kleine Ganzzahlen darzustellen, die sehr effizient gepickelt werden: ein Erweiterungscode im Bereich 1–255 erfordert nur zwei Bytes einschließlich des Opcodes, einer im Bereich 256–65535 erfordert nur drei Bytes einschließlich des Opcodes.
Eines der Designziele des Pickle-Protokolls ist es, Pickles „kontextfrei“ zu machen: Solange Sie die Module installiert haben, die die von einem Pickle referenzierten Klassen enthalten, können Sie es entpickeln, ohne eine dieser Klassen im Voraus importieren zu müssen.
Ungezügelte Nutzung von Erweiterungscodes könnte diese wünschenswerte Eigenschaft von Pickles gefährden. Daher ist die Hauptnutzung von Erweiterungscodes einer Reihe von Codes vorbehalten, die von einer Standardisierungsorganisation standardisiert werden. Da dies Python ist, ist die Standardisierungsorganisation die PSF. Von Zeit zu Zeit wird die PSF eine Tabelle festlegen, die Erweiterungscodes auf Klassennamen (oder gelegentlich Namen anderer globaler Objekte; Funktionen sind ebenfalls berechtigt) abbildet. Diese Tabelle wird in die nächste(n) Python-Version(en) aufgenommen.
Für einige Anwendungen, wie z. B. Zope, sind kontextfreie Pickles jedoch keine Anforderung, und das Warten auf die Standardisierung einiger Codes durch die PSF ist möglicherweise nicht praktikabel. Für solche Anwendungen werden zwei Lösungen angeboten.
Erstens sind einige Bereiche von Erweiterungscodes für die private Nutzung reserviert. Jede Anwendung kann Codes in diesen Bereichen registrieren. Zwei Anwendungen, die Pickles mit Codes aus diesen Bereichen austauschen, müssen einen Out-of-Band-Mechanismus haben, um die Zuordnung zwischen Erweiterungscodes und Namen zu vereinbaren.
Zweitens können einigen großen Python-Projekten (z. B. Zope) ein Bereich von Erweiterungscodes außerhalb des Bereichs „private Nutzung“ zugewiesen werden, die sie nach eigenem Ermessen zuweisen können.
Die Erweiterungsregistrierung ist als Abbildung zwischen Erweiterungscodes und Namen definiert. Wenn ein Erweiterungscode entpickelt wird, erzeugt er ein Objekt, aber dieses Objekt wird durch Interpretation des Namens als Modulname gefolgt von einem Klassen- (oder Funktions-) Namen erhalten. Die Abbildung von Namen zu Objekten wird zwischengespeichert. Es ist gut möglich, dass bestimmte Namen nicht importiert werden können; das sollte kein Problem sein, solange kein Pickle, das eine Referenz auf solche Namen enthält, entpickelt werden muss. (Das gleiche Problem besteht bereits für direkte Referenzen auf solche Namen in Pickles, die Protokolle 0 oder 1 verwenden.)
Hier ist die vorgeschlagene anfängliche Zuweisung von Erweiterungscodebereichen:
| Erste | Letzte | Anzahl | Zweck |
|---|---|---|---|
| 0 | 0 | 1 | Reserviert — wird niemals verwendet |
| 1 | 127 | 127 | Reserviert für die Python-Standardbibliothek |
| 128 | 191 | 64 | Reserviert für Zope |
| 192 | 239 | 48 | Reserviert für Dritte |
| 240 | 255 | 16 | Reserviert für private Nutzung (wird niemals zugewiesen) |
| 256 | MAX | MAX | Reserviert für zukünftige Zuweisung |
MAX steht für 2147483647 oder 2**31-1. Dies ist eine harte Grenze des aktuell definierten Protokolls.
Derzeit wurden noch keine spezifischen Erweiterungscodes zugewiesen.
Erweiterungsregistry API
Die Erweiterungsregistrierung wird als private globale Variablen im Modul copy_reg verwaltet. Die folgenden drei Funktionen sind in diesem Modul definiert, um die Registrierung zu manipulieren:
add_extension(module, name, code)- Registriert einen Erweiterungscode. Die Argumente module und name müssen Strings sein; code muss eine
intim inklusiven Bereich von 1 bis MAX sein. Dies muss entweder ein neues(module, name)-Paar einem neuen Code zuordnen oder eine redundante Wiederholung eines früheren Aufrufs sein, der nicht durch einenremove_extension()-Aufruf abgebrochen wurde; ein(module, name)-Paar darf nicht mehr als einem Code zugeordnet werden, noch darf ein Code mehr als einem(module, name)-Paar zugeordnet werden. remove_extension(module, name, code)- Die Argumente sind wie bei
add_extension(). Entfernt eine zuvor registrierte Zuordnung zwischen(module, name)und code. clear_extension_cache()- Die Implementierung von Erweiterungscodes kann einen Cache verwenden, um das Laden von häufig benannten Objekten zu beschleunigen. Dieser Cache kann geleert werden (Entfernung von Referenzen auf zwischengespeicherte Objekte), indem diese Methode aufgerufen wird.
Beachten Sie, dass die API die Standardbereichszuweisungen nicht erzwingt. Es liegt an den Anwendungen, diese zu respektieren.
Das copy Modul
Traditionell unterstützte das Modul copy eine erweiterte Teilmenge der Pickling-APIs zur Anpassung der Operationen copy() und deepcopy().
Insbesondere suchten copy() und deepcopy() neben der Suche nach einer __copy__- oder __deepcopy__-Methode immer nach __reduce__ und suchten für klassische Klassen nach __getinitargs__, __getstate__ und __setstate__.
In Python 2.2 ermöglichte die von 'object' geerbte Standardimplementierung von __reduce__ das Kopieren einfacher New-Style-Klassen, aber Slots und verschiedene andere Sonderfälle wurden nicht abgedeckt.
In Python 2.3 werden mehrere Änderungen am Modul copy vorgenommen:
__reduce_ex__wird unterstützt (und immer mit 2 als Argument für die Protokollversion aufgerufen).- Die Vier- und Fünfargument-Rückgabewerte von
__reduce__werden unterstützt. - Vor der Suche nach einer
__reduce__-Methode wird diecopy_reg.dispatch_tablekonsultiert, genau wie beim Pickeln. - Wenn die
__reduce__-Methode von object geerbt wird, wird sie (bedingungslos) durch eine bessere ersetzt, die dieselben APIs wie Pickle-Protokoll 2 verwendet:__getnewargs__,__getstate__und__setstate__, wobei Unterklassen vonlistunddictsowie Slots behandelt werden.
Als Folge der letzteren Änderung sind bestimmte New-Style-Klassen, die unter Python 2.2 kopierbar waren, unter Python 2.3 nicht mehr kopierbar. (Diese Klassen sind auch nicht mit Pickle-Protokoll 2 pickelbar.) Ein minimales Beispiel für eine solche Klasse:
class C(object):
def __new__(cls, a):
return object.__new__(cls)
Das Problem tritt nur auf, wenn __new__ überschrieben wird und mindestens ein obligatorisches Argument zusätzlich zum Klassenargument hat.
Um dies zu beheben, sollte eine __getnewargs__-Methode hinzugefügt werden, die das entsprechende Argumenttupel (ohne die Klasse) zurückgibt.
Pickling von Python-Longs
Das Pickeln und Entpickeln von Python-Long-Zahlen nimmt bei den Protokollen 0 und 1 quadratisch Zeit in Bezug auf die Anzahl der Ziffern in Anspruch. Unter Protokoll 2 unterstützen neue Opcodes das Pickeln und Entpickeln von Longs in linearer Zeit.
Pickling von Bools
Protokoll 2 führt neue Opcodes ein, um True und False direkt zu pickeln. Unter den Protokollen 0 und 1 werden boolesche Werte als ganze Zahlen gepickelt, wobei ein Trick in der Darstellung der ganzen Zahl im Pickle verwendet wird, so dass ein Entpickler erkennen kann, dass ein boolescher Wert gemeint war. Dieser Trick verbrauchte 4 Bytes pro gepickeltem booleschen Wert. Die neuen booleschen Opcodes verbrauchen 1 Byte pro booleschem Wert.
Pickling von kleinen Tupeln
Protokoll 2 führt neue Opcodes für ein kompakteres Pickeln von Tupeln der Längen 1, 2 und 3 ein. Protokoll 1 führte zuvor einen Opcode für ein kompakteres Pickeln von leeren Tupeln ein.
Protokollidentifikation
Protokoll 2 führt einen neuen Opcode ein, mit dem alle Protokoll 2 Pickles beginnen, der identifiziert, dass das Pickle Protokoll 2 ist. Der Versuch, ein Protokoll 2 Pickle unter älteren Python-Versionen zu entpickeln, führt daher sofort zu einer Ausnahme „unbekannter Opcode“.
Pickling von großen Listen und Dictionaries
Pickle-Protokoll 1 pickelt große Listen und Dictionaries „am Stück“, was die Pickle-Größe minimiert, aber erfordert, dass beim Entpickeln ein temporäres Objekt erstellt wird, das so groß ist wie das zu entpickelnde Objekt. Ein Teil der Protokoll 2-Änderungen zerlegt große Listen und Dictionaries in Teile von höchstens 1000 Elementen, so dass beim Entpickeln kein temporäres Objekt erstellt werden muss, das größer ist als zur Aufnahme von 1000 Elementen benötigt. Dies ist jedoch kein Teil von Protokoll 2: Die erzeugten Opcodes sind immer noch Teil von Protokoll 1. __reduce__-Implementierungen, die die optionalen neuen Iterator für Listen- oder Dictionary-Elemente zurückgeben, profitieren ebenfalls von dieser Optimierung des temporären Entpicklungsraums.
Urheberrecht
Dieses Dokument wurde gemeinfrei erklärt.
Quelle: https://github.com/python/peps/blob/main/peps/pep-0307.rst
Zuletzt geändert: 2025-02-01 08:59:27 GMT