Python:不可解なアイテムでDICTを漬けます
-
28-09-2019 - |
質問
オブジェクトがあります gui_project
属性があります .namespace
, 、名前空間のdictです。 (つまり、文字列からオブジェクトへのdict。)
(これはIDEのようなプログラムで使用され、ユーザーがPythonシェルで自分のオブジェクトを定義できるようにします。)
これを漬けたいです gui_project
, 、名前空間とともに。問題は、名前空間の一部のオブジェクト(つまりの値の値です .namespace
dict)は選択可能なオブジェクトではありません。たとえば、それらのいくつかはwxpythonウィジェットを参照しています。
未洗練されていないオブジェクトを除外したい、つまり、ピクルスバージョンからそれらを除外したいと思います。
これどうやってするの?
(私が試したことの1つは、値に1つずつ進んでそれらを漬けようとすることですが、無限の再帰が起こりました。それから安全である必要があります。)
(私はaを実装しています GuiProject.__getstate__
今の方法、他の象徴的でないものを取り除くために namespace
.)
解決 3
シェーンハサウェイのアプローチを使用して、私はこれに対する独自のソリューションをコーディングすることになりました。
これがコードです. 。 (探す CutePickler
と CuteUnpickler
.) これがテストです. 。それはの一部です garlicsim, 、したがって、あなたはそれを使用することができます インストール garlicsim
そしてやっている from garlicsim.general_misc import pickle_tools
.
Python 3コードで使用する場合は、 Python 3フォーク garlicsim
.
他のヒント
Picklerの文書化されたサポートを永続的なオブジェクト参照に使用します。永続的なオブジェクト参照は、ピクルスで参照されているがピクルスに保存されていないオブジェクトです。
http://docs.python.org/library/pickle.html#pickling-and-unpickling-external-objects
ZODBはこのAPIを何年も使用しているため、非常に安定しています。抑えるときは、オブジェクトの参照を好きなものに置き換えることができます。あなたの場合、オブジェクトの参照をマーカーに置き換えて、オブジェクトを漬けられないことを示します。
このようなもの(テストされていない)から始めることができます。
import cPickle
def persistent_id(obj):
if isinstance(obj, wxObject):
return "filtered:wxObject"
else:
return None
class FilteredObject:
def __init__(self, about):
self.about = about
def __repr__(self):
return 'FilteredObject(%s)' % repr(self.about)
def persistent_load(obj_id):
if obj_id.startswith('filtered:'):
return FilteredObject(obj_id[9:])
else:
raise cPickle.UnpicklingError('Invalid persistent id')
def dump_filtered(obj, file):
p = cPickle.Pickler(file)
p.persistent_id = persistent_id
p.dump(obj)
def load_filtered(file)
u = cPickle.Unpickler(file)
u.persistent_load = persistent_load
return u.load()
次に、pickle.dump()とpickle.load()の代わりに、dump_filtered()とload_filtered()を呼び出します。 WXPYTHONオブジェクトは、永続的なIDとして漬けられ、抑える時間にフィルター付きオブジェクトに置き換えられます。
組み込みのタイプではなく、ないオブジェクトを除外することにより、ソリューションをより一般的にすることができます __getstate__
方法。
アップデート (2010年11月15日):ラッパークラスで同じことを達成する方法があります。サブクラスの代わりにラッパークラスを使用すると、文書化されたAPI内にとどまることができます。
from cPickle import Pickler, Unpickler, UnpicklingError
class FilteredObject:
def __init__(self, about):
self.about = about
def __repr__(self):
return 'FilteredObject(%s)' % repr(self.about)
class MyPickler(object):
def __init__(self, file, protocol=0):
pickler = Pickler(file, protocol)
pickler.persistent_id = self.persistent_id
self.dump = pickler.dump
self.clear_memo = pickler.clear_memo
def persistent_id(self, obj):
if not hasattr(obj, '__getstate__') and not isinstance(obj,
(basestring, int, long, float, tuple, list, set, dict)):
return "filtered:%s" % type(obj)
else:
return None
class MyUnpickler(object):
def __init__(self, file):
unpickler = Unpickler(file)
unpickler.persistent_load = self.persistent_load
self.load = unpickler.load
self.noload = unpickler.noload
def persistent_load(self, obj_id):
if obj_id.startswith('filtered:'):
return FilteredObject(obj_id[9:])
else:
raise UnpicklingError('Invalid persistent id')
if __name__ == '__main__':
from cStringIO import StringIO
class UnpickleableThing(object):
pass
f = StringIO()
p = MyPickler(f)
p.dump({'a': 1, 'b': UnpickleableThing()})
f.seek(0)
u = MyUnpickler(f)
obj = u.load()
print obj
assert obj['a'] == 1
assert isinstance(obj['b'], FilteredObject)
assert obj['b'].about
これが私がこれを行う方法です(私は以前に似たようなことをしました、そしてそれはうまくいきました):
- オブジェクトが選択可能であるかどうかを判断する関数を書く
- 上記の関数に基づいて、すべての選択可能な変数のリストを作成します
- すべてのピックル不可能な変数を保存する新しい辞書(dと呼ばれる)を作成します
- dの各変数について(これは、dに非常に類似した変数がある場合にのみ機能します)文字列のリストを作成します。各文字列は法的なPythonコードであり、これらすべての文字列が順番に実行されると、目的の変数が取得されます。
今、あなたが解き放つと、元々ピックル可能だったすべての変数を取り戻します。選択できないすべての変数について、順番に実行されたときに希望の変数を提供する文字列(法律Pythonコード)のリストがあります。
お役に立てれば
1つのアプローチは、継承することです pickle.Pickler
, 、そしてオーバーライドします save_dict()
方法。このように読み取る基本クラスからコピーします。
def save_dict(self, obj):
write = self.write
if self.bin:
write(EMPTY_DICT)
else: # proto 0 -- can't use EMPTY_DICT
write(MARK + DICT)
self.memoize(obj)
self._batch_setitems(obj.iteritems())
ただし、_batch_setItemsでは、捨てられたくないすべてのアイテムを除去するイテレーターを渡します。
def save_dict(self, obj):
write = self.write
if self.bin:
write(EMPTY_DICT)
else: # proto 0 -- can't use EMPTY_DICT
write(MARK + DICT)
self.memoize(obj)
self._batch_setitems(item for item in obj.iteritems()
if not isinstance(item[1], bad_type))
Save_Dictは公式APIではないため、このオーバーライドがまだ正しいかどうかを新しいPythonバージョンごとに確認する必要があります。
フィルタリング部分は確かに注意が必要です。簡単なトリックを使用して、ピクルスを簡単に動作させることができます。ただし、フィルターが少し深く見えるときに保持できる情報を除外しすぎて、情報を失うことになります。しかし、に終わることができるものの大きな可能性 .namespace
構築を困難にします。
ただし、すでにPythonの一部であるピースを活用できます。 deepcopy
の中に copy
モジュール。
私は株のコピーを作りました copy
モジュール、そして次のことをしました:
- 名前の新しいタイプを作成します
LostObject
漬物で失われるオブジェクトを表すため。 - 変化する
_deepcopy_atomic
確かめるx
選択可能です。そうでない場合は、のインスタンスを返しますLostObject
- オブジェクトはメソッドを定義できます
__reduce__
および/または__reduce_ex__
それを漬ける方法と方法についてのヒントを提供する。これらの方法が例外を投げかけて、ピクルスできないというヒントを提供しないようにします。 - 大きなオブジェクトの不必要なコピーを作成しないようにala 実際のdeepcopy)、オブジェクトが選択可能かどうかを再帰的に確認し、象徴的でない部分のみを作成します。たとえば、ピックル可能なリストのタプルと挙げられないオブジェクトのために、タプルのコピーを作成します - そのメンバーリストではありません。
以下は差分です:
[~/Development/scratch/] $ diff -uN /System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/copy.py mcopy.py
--- /System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/copy.py 2010-01-09 00:18:38.000000000 -0800
+++ mcopy.py 2010-11-10 08:50:26.000000000 -0800
@@ -157,6 +157,13 @@
cls = type(x)
+ # if x is picklable, there is no need to make a new copy, just ref it
+ try:
+ dumps(x)
+ return x
+ except TypeError:
+ pass
+
copier = _deepcopy_dispatch.get(cls)
if copier:
y = copier(x, memo)
@@ -179,10 +186,18 @@
reductor = getattr(x, "__reduce_ex__", None)
if reductor:
rv = reductor(2)
+ try:
+ x.__reduce_ex__()
+ except TypeError:
+ rv = LostObject, tuple()
else:
reductor = getattr(x, "__reduce__", None)
if reductor:
rv = reductor()
+ try:
+ x.__reduce__()
+ except TypeError:
+ rv = LostObject, tuple()
else:
raise Error(
"un(deep)copyable object of type %s" % cls)
@@ -194,7 +209,12 @@
_deepcopy_dispatch = d = {}
+from pickle import dumps
+class LostObject(object): pass
def _deepcopy_atomic(x, memo):
+ try:
+ dumps(x)
+ except TypeError: return LostObject()
return x
d[type(None)] = _deepcopy_atomic
d[type(Ellipsis)] = _deepcopy_atomic
今、漬物に戻ります。この新しいものを使用してディープコピーを作成するだけです deepcopy
機能し、コピーを漬けます。象徴的でない部品は、コピープロセス中に削除されました。
x = dict(a=1)
xx = dict(x=x)
x['xx'] = xx
x['f'] = file('/tmp/1', 'w')
class List():
def __init__(self, *args, **kwargs):
print 'making a copy of a list'
self.data = list(*args, **kwargs)
x['large'] = List(range(1000))
# now x contains a loop and a unpickable file object
# the following line will throw
from pickle import dumps, loads
try:
dumps(x)
except TypeError:
print 'yes, it throws'
def check_picklable(x):
try:
dumps(x)
except TypeError:
return False
return True
class LostObject(object): pass
from mcopy import deepcopy
# though x has a big List object, this deepcopy will not make a new copy of it
c = deepcopy(x)
dumps(c)
cc = loads(dumps(c))
# check loop refrence
if cc['xx']['x'] == cc:
print 'yes, loop reference is preserved'
# check unpickable part
if isinstance(cc['f'], LostObject):
print 'unpicklable part is now an instance of LostObject'
# check large object
if loads(dumps(c))['large'].data[999] == x['large'].data[999]:
print 'large object is ok'
これが出力です:
making a copy of a list
yes, it throws
yes, loop reference is preserved
unpicklable part is now an instance of LostObject
large object is ok
1)相互のポインター(間に x
と xx
)保存されており、無限のループに遭遇しません。 2)抑制できないファイルオブジェクトは LostObject
実例; 3)大型オブジェクトの新しいコピーは、選択可能であるため作成されていません。