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

Python Enhancement Proposals

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

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 __newobj__ für einen Sonderfall (neu in diesem PEP) hier.

Argumente Obligatorisch.

Ein Tupel, das die Argumentliste für die Funktion angibt. Als Sonderfall, der für ExtensionClass von Zope 2 entwickelt wurde, kann dies None sein; in diesem Fall sollte Funktion eine Klasse oder ein Typ sein, und function.__basicnew__() wird aufgerufen, um die initiale Version des Objekts zu erstellen. Diese Ausnahme ist veraltet.

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 None ist, wird der Zustand gepickelt, und obj.__setstate__(state) wird beim Entpickeln aufgerufen. Wenn keine __setstate__-Methode definiert ist, wird eine Standardimplementierung bereitgestellt, die davon ausgeht, dass der Zustand ein Dictionary ist, das Instanzvariablennamen ihren Werten zuordnet. Die Standardimplementierung ruft auf:

obj.__dict__.update(state)

oder, wenn der update()-Aufruf fehlschlägt:

for k, v in state.items():
    setattr(obj, k, v)
Listen-Elemente Optional, und neu in diesem PEP.

Wenn dies nicht None ist, sollte es ein Iterator (keine Sequenz!) sein, der nacheinander Listen-Elemente liefert. Diese Listen-Elemente werden gepickelt und dem Objekt entweder mittels obj.append(item) oder obj.extend(list_of_items) hinzugefügt. Dies wird hauptsächlich für list-Unterklassen verwendet, kann aber auch von anderen Klassen verwendet werden, solange diese append()- und extend()-Methoden mit der entsprechenden Signatur haben. (Ob append() oder extend() verwendet wird, hängt von der verwendeten Pickle-Protokollversion sowie der Anzahl der hinzuzufügenden Elemente ab, daher müssen beide unterstützt werden.)

Dict-Elemente Optional, und neu in diesem PEP.

Wenn dies nicht None ist, sollte es ein Iterator (keine Sequenz!) sein, der nacheinander Dictionary-Elemente liefert, die Tupel der Form (key, value) sein sollten. Diese Elemente werden gepickelt und im Objekt mittels obj[key] = value gespeichert. Dies wird hauptsächlich für dict-Unterklassen verwendet, kann aber auch von anderen Klassen verwendet werden, solange sie __setitem__ implementieren.

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:

  1. Instanzen klassischer Klassen, alle Protokolle
  2. Instanzen neuer Klassen, Protokolle 0 und 1
  3. 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 einer RuntimeError Ausnahme fehlschlägt, ruft sie setattr(self, key, value) für jedes (key, value)-Paar im Zustandsdictionary auf. Dies geschieht nur beim Entpickeln im eingeschränkten Ausführungsmodus (siehe das Modul rexec der 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 in copy_reg registrierte __reduce__ Implementierung unterstützt.) In Jython ist PyStringMap ebenfalls 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-__dict__ und keine __slots__ hat, ist der Standardzustand None.
  • Für eine New-Style-Klasse, die ein Instanz-__dict__ und keine __slots__ hat, ist der Standardzustand self.__dict__.
  • Für eine New-Style-Klasse, die ein Instanz-__dict__ und __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-__dict__ hat, ist der Standardzustand ein Tupel, dessen erstes Element None ist 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 int im 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 einen remove_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 die copy_reg.dispatch_table konsultiert, 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 von list und dict sowie 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.


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

Zuletzt geändert: 2025-02-01 08:59:27 GMT