pickle ist ein sehr leistungsfähiger Mechanismus in Pythons einzigartigem Daten-Serialisierungsformat, aber das Verhalten dahinter ist weniger flexibel und einfacher als in der Vergangenheit. Hier fassen wir den Prozess des Beizens und Entpickelns von nicht integrierten Hauptklassen (kleinere eingebaute Klassen, Standard- / Nicht-Standardbibliotheken, benutzerdefinierte Klassen usw.) und das effiziente Beizen von Benutzern zusammen. Ich habe zusammengefasst, ob eine Definitionsklasse erstellt werden kann.
Die Diskussion hier basiert auf Python 3.3.5 und Pickle Protocol Version 3. Das Protokoll Version 4 wurde aus Python 3.4 eingeführt, aber die interne Verarbeitung ist komplizierter geworden. Ich denke, es wäre effizient, es zuerst mit dem Code von Python 3.3 zu verstehen.
Sie können dies hauptsächlich anhand der folgenden Methode verstehen.
Lib/pickle.py
class _Pickler:
def save(self, obj, save_persistent_id=True):
...
def save_reduce(self, func, args, state=None,
listitems=None, dictitems=None, obj=None):
...
def save_global(self, obj, name=None, pack=struct.pack):
...
Objects/typeobject.c
static PyObject *
reduce_2(PyObject *obj)
{
...
}
static PyObject *
object_reduce_ex(PyObject *self, PyObject *args)
{
...
}
Wenn pickle.dump, pickle.dumps
usw. aufgerufen werden, wird alles durch die folgende Verarbeitung in pickle konvertiert.
sample1.py
pickler = pickle.Pickler(fileobj, protocol)
pickler.dump(obj)
Die Pickler-Klasse ist
_pickle.Pickler
oderpickle._Pickler
Es gibt also Entitäten an den folgenden Stellen.static PyTypeObject Pickler_Type;
definiert in Modules / _pickler.cclass _Pickler
definiert in Lib / pickle.py
Normalerweise wird die C-Implementierung bevorzugt verwendet. Wenn der Import jedoch fehlschlägt, wird die Python-Implementierung verwendet.
Da der Hauptzweck hier darin besteht, den Mechanismus zu verstehen, konzentrieren wir uns auf die Python-Implementierung.Einzelne Objekte werden von pickler.save (obj)
rekursiv gebeizt.
Zunächst werden vorhandene Objekte wie Zirkelreferenzen und Referenzen an mehreren Stellen in der ersten Hälfte dieser Funktion entsprechend als Vorwärtsreferenzen ausgewählt.
Da die folgenden integrierten Klassen und Konstanten häufig verwendet werden, implementiert Pickle eine eigene effiziente Verarbeitung.
Aus diesem Grund entspricht es nicht der Erklärung in diesem Artikel und wird hier weggelassen.
int, float, str, bytes, list, tuple, dict, bool, None
Für andere Klassen wird es nach dem unten gezeigten Verfahren eingelegt.
Wenn das Pickle-Ziel ein Klassenobjekt (dh "isinstance (obj, type) == True") oder eine Funktion ist, wird "obj .__ module__, obj .__ name__" als Zeichenfolge aufgezeichnet. Bei der Unpickle-Konvertierung wird nach dem Import des erforderlichen Moduls der Wert, auf den dieser Variablenname verweisen kann, aufgehoben. Das heißt, nur Klassen und Funktionen, die im globalen Namespace des Moduls definiert sind, können ausgewählt werden. Natürlich wird die Logik von Funktionen und Klassen nicht berücksichtigt, Python ist nicht LISP.
Als nächstes wird die Existenz von "copyreg.dispatch_table [type (obj)]" aus dem im copyreg-Modul global definierten Wörterbuch überprüft.
sample02.py
import copyreg
if type(obj) in copyreg.dispatch_table:
reduce = copyreg.dispatch_table[type(obj)]
rv = reduce(obj)
Der Inhalt des Rückgabewerts "rv" wird später beschrieben.
Auf diese Weise hat die in "copyreg.dispatch_table" registrierte Funktion die höchste Priorität und wird zur Beizung verwendet. Daher kann auch eine Klasse, deren Definition nicht geändert werden kann, das Verhalten von pickle / unpickle ändern. Im Extremfall können Sie ein Zeitobjekt zum Ein- / Ausheben eines Zeitobjekts machen, indem Sie es zu einem Objekt mit regulären Ausdrücken machen.
sample03.py
import pickle
import copyreg
import datetime
import re
def reduce_datetime_to_regexp(x):
return re.compile, (r'[spam]+',)
copyreg.pickle(datetime.datetime, reduce_datetime_to_regexp)
a = datetime.datetime.now()
b = pickle.loads(pickle.dumps(a))
print(a, b) # 2014-10-05 10:24:12.177959 re.compile('[spam]+')Ausgabe wie
Das Hinzufügen zum Wörterbuch dispatch_table
erfolgt über copyreg.pickle (type, func)
.
Wenn es ein Wörterbuch "pickler.dispatch_table" gibt, wird dieses anstelle von "copyreg.dispatch_table" verwendet. Dies ist sicherer, wenn Sie das Verhalten nur beim Beizen für einen bestimmten Zweck ändern möchten.
sample03a.py
import pickle
import copyreg
import datetime
import re
import io
def reduce_datetime_to_regexp(x):
return re.compile, (r'[spam]+',)
a = datetime.datetime.now()
with io.BytesIO() as fp:
pickler = pickle.Pickler(fp)
pickler.dispatch_table = copyreg.dispatch_table.copy()
pickler.dispatch_table[datetime.datetime] = reduce_datetime_to_regexp
pickler.dump(a)
b = pickle.loads(fp.getvalue())
print(a, b) # 2014-10-05 10:24:12.177959 re.compile('[spam]+')Ausgabe wie
obj .__ redu_ex__
definiert istWenn die Methode obj .__ redu_ex__
definiert ist
sample03.py
rv = obj.__reduce_ex__(protocol_version)
Wird genannt. Der Inhalt des Rückgabewerts "rv" wird später beschrieben.
obj .__ redu____
definiert istWenn die Methode obj .__ redu____
definiert ist
sample03.py
rv = obj.__reduce__()
Wird genannt. Der Inhalt des Rückgabewerts "rv" wird später beschrieben.
Es scheint, dass es nicht die aktuelle Situation ist. Sie sollten immer __reduce_ex__
verwenden.
Dies wird zuerst gesucht, damit es etwas schneller geht.
Wenn Sie die Protokollvariable nicht verwenden, können Sie sie ignorieren.
Wenn keine spezielle Methode für Pickle / Unpickle geschrieben wurde, wird als letztes Mittel der "Objekt" -Standard "-Reduktions" -Prozess durchgeführt. Dies ist sozusagen "die universellste und maximalste Commitment-Implementierung" reduce_ex ", die wie für die meisten Objekte verwendet werden kann", was sehr hilfreich ist, aber leider in C-Sprache und -Verständnis implementiert ist schwer. Wenn dieser Teil wie die Fehlerbehandlung weggelassen wird und der allgemeine Ablauf in Python implementiert ist, sieht er wie folgt aus.
object_reduce_ex.py
class object:
def __reduce_ex__(self, proto):
from copyreg import __newobj__
if hasattr(self, '__getnewargs__'):
args = self.__getnewargs__()
else:
args = ()
if hasattr(self, '__getstate__'):
state = self.__getstate__()
elif hasattr(type(self), '__slots__'):
state = self.__dict__, {k: getattr(self, k) for k in type(self).__slots__}
else:
state = self.__dict__
if isinstance(self, list):
listitems = self
else:
listitems = None
if isinstance(self, dict):
dictitems = self.items()
else:
listitems = None
return __newobj__, (type(self),)+args, state, listitems, dictitems
Wie Sie oben sehen können, können Sie das Verhalten im Detail ändern, indem Sie die Methoden von getnewargs, getstatedefinieren, auch wenn Sie sich auf
object .__ redu_ex__` verlassen.
Wenn Sie "reduce_ex", "reduce" selbst definieren, werden diese Funktionen nur verwendet, wenn Sie sie explizit aufrufen.
__getnewargs__
Eine Methode, die einen Taple zurückgibt, der eingelegt werden kann.
Sobald dies definiert ist, können die Argumente für "new" bei der Unpickleization (nicht "init") angepasst werden.
Enthält nicht das erste Argument (Klassenobjekt).
__getstate__
Wenn dies definiert ist, kann das Argument von "setstate" bei der Unpickleization oder der Anfangswert von "dict" und der Slot, wenn "setstate" nicht vorhanden ist, angepasst werden.
__reduce_ex__, __reduce__
und copyreg zurückgegeben werden sollenIn dem obigen Prozess ist der Wert "rv", den jede Funktion zurückgeben sollte
Ist.
type (rv) str
isttype (obj) .__ module__, rv
wird bei der Pickle-Konvertierung als Zeichenfolge aufgezeichnet. Bei der Unpickle-Konvertierung wird das Modul ordnungsgemäß importiert und das Objekt, auf das dieser Name verweist, wird zurückgegeben.
Dieser Mechanismus kann effektiv verwendet werden, wenn ein Singleton-Objekt oder dergleichen gebeizt wird.
type (rv) tuple
istDie Elemente des Taples (2 oder mehr und 5 oder weniger) sind wie folgt
func
- Ein auswählbares und aufrufbares Objekt (normalerweise ein Klassenobjekt), das beim Aufheben der Auswahl ein Objekt erstellt. Im Fall von func .__ name__ ==" __newobj__ "
wird die Ausnahme jedoch später beschrieben.args
--pickle Ein Taple möglicher Elemente. Wird als Parameter beim Aufruf von func
verwendet.state
- Ein Objekt zum Aufheben der Auswahl des Status eines Objekts. Optional. Es kann "Keine" sein.listitems
- ein iterierbares Objekt, das Elemente eines list
-ähnlichen Objekts zurückgibt. Optional. Es kann "Keine" sein.dictitems
- ein iterierbares Objekt, das die Schlüssel und Elemente einesdict
-ähnlichen Objekts zurückgibt. Der vom Iterator zurückgegebene Wert muss ein Schlüssel / Element-Paar sein. Normalerweise "dict_object.items ()". Optional. Es kann "Keine" sein.func .__ name__ ==" __newobj__ "
In diesem Fall wird "args [0]" als Klassenobjekt interpretiert und ein Klassenobjekt mit "args" als Argument erstellt. Zu diesem Zeitpunkt wird __init__
nicht aufgerufen.
Wenn Sie ein func-Objekt mit diesen Bedingungen benötigen, haben Sie bereits eines im copyreg-Modul deklariert.
Lib/copyreg.py
def __newobj__(cls, *args):
return cls.__new__(cls, *args)
Dieses copyreg .__ newobj__
wird implementiert und eingegeben, so dass es sich genauso verhält, auch wenn es als normale Funktion interpretiert wird, aber nicht tatsächlich ausgeführt wird.
Es wird wie folgt interpretiert.
obj .__ items__
Sie können dies hauptsächlich anhand der folgenden Methode verstehen.
Lib/pickle.py
class _Unpickler:
def load_newobj(self):
...
def load_reduce(self):
...
def load_build(self):
...
def load_global(self):
...
Wenn pickle.load, pickle.loads
usw. aufgerufen werden, wird durch die folgende Verarbeitung alles aufgehoben.
sample1.py
unpickler = pickle.Unpickler(fileobj)
unpickler.load()
Die Unpickler-Klasse ist
_pickle.Unpickler
oderpickle._Unpickler
Es gibt also Entitäten an den folgenden Stellen.static PyTypeObject Unpickler_Type;
definiert in Modules / _pickler.cclass _Unpickler
definiert in Lib / pickle.pyDas Objekt wird wiederhergestellt, während nacheinander "unpickler.load_xxx ()" gemäß der ID aufgerufen wird, die als Opcode bezeichnet wird, entsprechend dem Element in den Pickle-Daten.
In Fällen, in denen eine Klasse, Funktion oder __reduce_ex__
eine Zeichenfolge zurückgibt, wird die Zeichenfolge" modulename.varname "
unverändert aufgezeichnet.
Importieren Sie in diesem Fall ggf. das Modul und geben Sie den entsprechenden Wert aus.
Unpickler erstellt kein neues Objekt.
Bei der Beizung mit dem von __reduce_ex__
usw. zurückgegebenen 5-Element-Tapple wird das Objekt durch diese Prozesse entpickt.
Wenn Sie den Umriss jeder Methode von "load_newobj, load_reduce, load_build", die diesem Prozess entspricht, in einem einfachen Ablauf neu schreiben, sieht dies wie folgt aus.
sample09.py
def unpickle_something():
func, args, state, listitems, dictitems = load_from_pickle_stream()
if getattr(func, '__name__', None) == '__newobj__':
obj = args[0].__new__(*args)
else:
obj = func(*args)
if lisitems is not None:
for x in listitems:
obj.append(x)
if dictitems is not None:
for k, v in dictitems:
obj[k] = v
if hasattr(obj, '__setstate__'):
obj.__setstate__(state)
elif type(state) is tuple and len(state) == 2:
for k, v in state[0].items():
obj.__dict__[k] = v
for k, v in state[1].items():
setattr(obj, k, v)
else:
for k, v in state.items():
obj.__dict__[k] = v
return obj
Fälle, die die folgenden Bedingungen erfüllen, können ordnungsgemäß verarbeitet werden, ohne die Pickle- und Unpickle-Prozesse zu schreiben.
__init__
nicht aufgerufen wird, gibt es keine Inkonsistenz als Objekt, wenn die Attribute korrekt wiederhergestellt werden.sphere0.py
import pickle
class Sphere:
def __init__(self, radius):
self._radius = radius
@property
def volume(self):
if not hasattr(self, '_volume'):
from math import pi
self._volume = 4/3 * pi * self._radius ** 3
return self._volume
def _main():
sp1 = Sphere(3)
print(sp1.volume)
print(sp1.__reduce_ex__(3))
sp2 = pickle.loads(pickle.dumps(sp1))
print(sp2.volume)
if __name__ == '__main__':
_main()
Wenn das Shere-Objekt, das eine Kugel darstellt, auf die Volume-Eigenschaft zugreift, die das Volume darstellt, wird das Berechnungsergebnis intern zwischengespeichert. Wenn dies so wie es ist eingelegt wird, wird das zwischengespeicherte Volume zusammen gespeichert und der dritte Datenwert erhöht sich. Ich möchte dies löschen.
sphere1.py
class Sphere:
def __init__(self, radius):
self._radius = radius
@property
def volume(self):
if not hasattr(self, '_volume'):
from math import pi
self._volume = 4/3 * pi * self._radius ** 3
return self._volume
def __getstate__(self):
return {'_radius': self._radius}
Sie können verhindern, dass der Cache eingelegt wird, indem Sie eine "getstate" -Methode definieren, die nach dem Aufheben der Auswahl den Wert von "__dict __" zurückgibt.
sphere2.py
class Sphere:
__slots__ = ['_radius', '_volume']
def __init__(self, radius):
self._radius = radius
@property
def volume(self):
if not hasattr(self, '_volume'):
from math import pi
self._volume = 4/3 * pi * self._radius ** 3
return self._volume
def __getstate__(self):
return None, {'_radius': self._radius}
Wenn Sie "slots" definieren, ist "dict" nicht mehr vorhanden und Sie müssen den von "getstate" zurückgegebenen Wert ändern, um die Speichereffizienz zu verbessern.
In diesem Fall handelt es sich um einen Taple mit zwei Elementen, und das letztere Element ist ein Wörterbuch, das die Attribute von __slots__
initialisiert.
Das vorherige Element (Anfangswert von __dict__
) kann None
sein.
sphere3.py
class Sphere:
__slots__ = ['_radius', '_volume']
def __init__(self, radius):
self._radius = radius
@property
def volume(self):
if not hasattr(self, '_volume'):
from math import pi
self._volume = 4/3 * pi * self._radius ** 3
return self._volume
def __getstate__(self):
return self._radius
def __setstate__(self, state):
self._radius = state
Wenn der einzige Wert, der ausgewählt werden soll, der Radius ist, können Sie den Wert "self._radius" selbst anstelle des Wörterbuchs als "getstate" zurückgeben.
Definieren Sie in diesem Fall auch einen gepaarten __setstate__
.
__new__
entsprechende Argumente zu gebenintliterals.py
import pickle
class IntLiterals(tuple):
def __new__(cls, n):
a = '0b{n:b} 0o{n:o} {n:d} 0x{n:X}'.format(n=n).split()
return super(cls, IntLiterals).__new__(cls, a)
def __getnewargs__(self):
return int(self[0], 0),
def _main():
a = IntLiterals(10)
print(a) # ('0b1010', '0o12', '10', '0xA')
print(a.__reduce_ex__(3))
b = pickle.loads(pickle.dumps(a))
print(b)
if __name__ == '__main__':
_main()
__init__
aufzurufenclosureholder.py
import pickle
class ClosureHolder:
def __init__(self, value):
def _get():
return value
self._get = _get
def get(self):
return self._get()
def __reduce_ex__(self, proto):
return type(self), (self.get(),)
def _main():
a = ClosureHolder('spam')
print(a.get())
print(a.__reduce_ex__(3))
b = pickle.loads(pickle.dumps(a))
print(b.get())
if __name__ == '__main__':
_main()
Der von get
zurückgegebene Wert wird beim Schließen in __init__
gespeichert, sodass das Objekt nicht ohne Aufruf von __init__
erstellt werden kann.
In einem solchen Fall kann object .__ redu_ex__
nicht verwendet werden, also implementieren Sie__reduce_ex__
selbst.
singleton.py
class MySingleton(object):
def __new__(cls, *args, **kwds):
assert mysingleton is None, \
'A singleton of MySingleton has already been created.'
return super(cls, MySingleton).__new__(cls, *args, **kwds)
def __reduce_ex__(self, proto):
return 'mysingleton'
mysingleton = None
mysingleton = MySingleton()
def _main():
import pickle
a = pickle.dumps(mysingleton)
b = pickle.loads(a)
print(b)
if __name__ == '__main__':
_main()
Angenommen, die MySingleton-Klasse hat nur eine Instanz in der globalen Variablen mysingleton.
Um dies korrekt aufzuheben, verwenden Sie ein Format, in dem __reduce_ex__
eine Zeichenfolge zurückgibt.
Recommended Posts