بيثون: التخليل مع بعض العناصر التي لا يمكن تخليصها

StackOverflow https://stackoverflow.com/questions/4080688

سؤال

لدي كائن 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

هذه هي الطريقة التي سأفعل بها هذا (فعلت شيئًا مشابهًا من قبل وعمله):

  1. اكتب وظيفة تحدد ما إذا كان كائن ما يمكن أن يكون قابلاً للانتقاء أم لا
  2. قم بعمل قائمة بجميع المتغيرات القابلة للتقاط ، استنادًا إلى الوظيفة أعلاه
  3. اصنع قاموسًا جديدًا (يسمى D) الذي يخزن جميع المتغيرات غير القابلة للاختراق
  4. لكل متغير في 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 الوحدة ، وفعلت الأشياء التالية:

  1. قم بإنشاء نوع جديد اسمه LostObject لتمثيل كائن سيضيع في التخليل.
  2. يتغيرون _deepcopy_atomic للتأكد x هو قابلية للانتقاء. إذا لم يكن الأمر كذلك ، فأعود مثيلًا LostObject
  3. يمكن للكائنات تحديد الطرق __reduce__ و/أو __reduce_ex__ لتوفير تلميح حول ما إذا كانت وكيفية المخلل. نتأكد من أن هذه الأساليب لن ترمي استثناءً لتوفير تلميح بأنه لا يمكن مخللها.
  4. لتجنب صنع نسخة غير ضرورية من الكائنات الكبيرة (لوس أنجلوس 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) لا يتم إنشاء نسخة جديدة من الكائن الكبير لأنه قابل للانتقاء.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top