بيثون: التخليل مع بعض العناصر التي لا يمكن تخليصها
-
28-09-2019 - |
سؤال
لدي كائن gui_project
التي لها سمة .namespace
, ، وهو قول مساحة الاسم. (أييل من الأوتار إلى الأشياء.)
(يتم استخدام هذا في برنامج يشبه IDE للسماح للمستخدم بتحديد كائنه الخاص في قذيفة Python.)
اريد انخفض هذا gui_project
, ، جنبا إلى جنب مع مساحة الاسم. المشكلة هي أن بعض الكائنات في مساحة الاسم (أي قيم .namespace
DICT) ليست كائنات قابلة للانتقاء. على سبيل المثال ، يشير بعضهم إلى Wxpython عناصر واجهة المستخدم.
أرغب في تصفية الكائنات التي لا يمكن تخليصها ، أي استبعادها من الإصدار المخلل.
كيف يمكنني أن أفعل هذا؟
(شيء واحد حاولت أن أذهب واحدًا تلو الآخر على القيم ومحاولة المخلل ، لكن بعض العودية اللانهائية حدثت ، وأحتاج إلى أن أكون في مأمن من ذلك.)
(أنا أقوم بتنفيذ أ GuiProject.__getstate__
الطريقة الآن ، للتخلص من الأشياء الأخرى التي لا يمكن تخليصها إلى جانب namespace
.)
المحلول 3
انتهى بي الأمر بترميز الحل الخاص بي لهذا ، باستخدام نهج شين هاثاواي.
ها هي الرمز. (يبحث عن CutePickler
و CuteUnpickler
.) ها هي الاختبارات. إنه جزء من Garlicsim, ، حتى تتمكن من استخدامه بواسطة تثبيت garlicsim
والقيام from garlicsim.general_misc import pickle_tools
.
إذا كنت ترغب في استخدامه على رمز Python 3 ، استخدم بيثون 3 شوكة garlicsim
.
نصائح أخرى
أود استخدام دعم Pickler الموثق لمراجع الكائنات المستمرة. مراجع الكائنات المستمرة هي كائنات مشار إليها بواسطة المخلل ولكن لم يتم تخزينها في المخلل.
http://docs.python.org/library/pickle.html#pickling-and-unpickling-oxternal-objects
استخدم Zodb واجهة برمجة التطبيقات هذه لسنوات ، لذلك فهي مستقرة للغاية. عند إلغاء الاختيار ، يمكنك استبدال مراجع الكائن بأي شيء تريده. في حالتك ، تريد استبدال مراجع الكائن بعلامات تشير إلى أنه لا يمكن مخلل الكائنات.
يمكنك أن تبدأ بشيء من هذا القبيل (لم يتم اختباره):
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()
ثم فقط استدعاء dump_filtered () و load_filtered () بدلاً من pickle.dump () و pickle.load (). سيتمخل كائنات Wxpython كمعرفات مستمرة ، ليتم استبدالها بالتصفيات في وقت فك الوقت.
يمكنك جعل الحل أكثر عاما عن طريق تصفية الكائنات التي ليست من الأنواع المدمجة وليس لها لا __getstate__
طريقة.
تحديث (15 نوفمبر 2010): فيما يلي طريقة لتحقيق نفس الشيء مع فصول الغلاف. باستخدام فئات Wrapper بدلاً من الفئات الفرعية ، من الممكن البقاء ضمن واجهة برمجة التطبيقات الموثقة.
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 القانوني) الذي يمنحك المتغير المطلوب عند تنفيذه.
أتمنى أن يساعدك هذا
نهج واحد هو أن ترث من 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 ليس واجهة برمجة تطبيقات رسمية ، فأنت بحاجة إلى التحقق من كل إصدار Python جديد ما إذا كان هذا التجاوز لا يزال صحيحًا.
جزء التصفية صعب بالفعل. باستخدام الحيل البسيطة ، يمكنك بسهولة الحصول على المخلل للعمل. ومع ذلك ، قد ينتهي بك الأمر إلى التصفية أكثر من اللازم وفقدان المعلومات التي يمكنك الاحتفاظ بها عندما يبدو المرشح أعمق قليلاً. لكن الإمكانية الواسعة للأشياء التي يمكن أن تنتهي في .namespace
يجعل بناء مرشح جيد صعبًا.
ومع ذلك ، يمكننا الاستفادة من القطع التي هي بالفعل جزء من بيثون ، مثل deepcopy
في ال copy
وحدة.
لقد صنعت نسخة من الأسهم copy
الوحدة ، وفعلت الأشياء التالية:
- قم بإنشاء نوع جديد اسمه
LostObject
لتمثيل كائن سيضيع في التخليل. - يتغيرون
_deepcopy_atomic
للتأكدx
هو قابلية للانتقاء. إذا لم يكن الأمر كذلك ، فأعود مثيلًاLostObject
- يمكن للكائنات تحديد الطرق
__reduce__
و/أو__reduce_ex__
لتوفير تلميح حول ما إذا كانت وكيفية المخلل. نتأكد من أن هذه الأساليب لن ترمي استثناءً لتوفير تلميح بأنه لا يمكن مخللها. - لتجنب صنع نسخة غير ضرورية من الكائنات الكبيرة (لوس أنجلوس Deepcopy الفعلي) ، نتحقق بشكل متكرر ما إذا كان الكائن قابلًا للالتقاط ، ونجعل جزءًا لا يمكن تخليصه فقط. على سبيل المثال ، للحصول على مجموعة من القائمة القابلة للتصوير وكائن لا يمكن تخويده ، سنقوم بإنشاء نسخة من tuple - فقط الحاوية - ولكن ليس قائمة الأعضاء الخاصة بها.
ما يلي هو الفرق:
[~/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) لا يتم إنشاء نسخة جديدة من الكائن الكبير لأنه قابل للانتقاء.