如何检查复杂对象的哪些细节不能被腌制

Ber*_*ard 19 python debugging pickle python-2.7 python-3.x

概观

我想序列化我的复杂对象.它看起来很简单,但每一步都会产生不同的问题.

最后,其他程序员还必须能够创建从父对象继承的复杂对象.对于Python 2.7和Python3.x,这个对象应该是pickleable.

我从一个简单的对象开始,并使用pickle.dumppickle.load成功.

然后我创建了多个复杂的对象(类似但不完全相同),其中一些可以被转储,而有些则不能.

调试

pickle库知道哪些对象可以被腌制.理论上,这种方法pdb可以定制,以启用泡菜调试.

替代序列化库

我想要一个独立于对象内容的可靠序列化.所以我搜索了其他序列化工具:

  • 自我测试失败并且似乎过时的Cerealizer.
  • MessagePack不适用于Python 3.
  • 我试过JSON并得到了错误: builtins.TypeError: <lib.scan.Content object at 0x7f37f1e5da50> is not JSON serializable
  • 我看着Marshal和Shelve,但都参考了Pickle.

深入研究使用泡菜

我已经阅读了如何检查一个物体是否可以拾取而没有给我一个答案.

我找到的最接近的是如何在大量物体上找到Python Pickle中的错误来源

我把它调整为:

import pickle

if _future_.isPython3():        
    class MyPickler(pickle._Pickler):        
        def save(self, obj):             
            try:
                pickle._Pickler.save(self, obj)
            except:
                print ('pick(3.x) {0} of type {1}'.format(obj, type(obj)))                  
else:
    class MyPickler (pickle.Pickler):

        def save(self, obj):         
            try:
                pickle.Pickler.save(self, obj)
            except:
                print('pick(2.x)', obj, 'of type', type(obj))
Run Code Online (Sandbox Code Playgroud)

我用以下方法调用此代码:

def save(obj, file):  
    if platform.python_implementation() == 'CPython':
        myPickler = MyPickler(file)                
        myPickler.save(obj) 
Run Code Online (Sandbox Code Playgroud)

我希望执行保存直到引发异常.obj打印的内容,以便我可以确切地看到错误发生的位置.但结果是:

pick(3.x)  <class 'module'> of type <class 'type'>
pick(3.x)  <class 'module'> of type <class 'type'>
pick(3.x)  <class 'Struct'> of type <class 'type'>
pick(3.x)  <class 'site.setquit.<locals>.Quitter'> of type <class 'type'>
pick(3.x)  <class 'site.setquit.<locals>.Quitter'> of type <class 'type'>
pick(3.x)  <class 'module'> of type <class 'type'>
pick(3.x)  <class 'sys.int_info'> of type <class 'type'>
...
Run Code Online (Sandbox Code Playgroud)

这只是结果的一小部分.我不理解这一点.它对我来说没有哪个细节错误.以及如何解决这个问题.

我已经看到:http://docs.python.org/3/library/pickle.html#what-c​​an-be-pickled-and-unpickled但是如果我无法检测到代码中的哪一行不能帮助我被腌制.

我的复杂对象中的代码按预期工作,最终运行生成的代码为:

sys.modules['unum']
Run Code Online (Sandbox Code Playgroud)

但是在酸洗时,似乎"模块"没有按预期读取.

尝试解决方案

一些背景清楚我的意思.我有过工作的程序,突然没有工作.它可能是更新或其他更改资源.为他人工作但不适合我和我的工作的程序.

这是一个普遍的问题,所以我想开发一个程序来检查所有类型的资源.不同种类资源的数量巨大.所以我有一个具有所有一般行为的父对象类.并为特定资源尽可能小的详细类.

这是在我的子资源类中完成的.

必须使用Python 2.7或Python 3.3的不同版本检查这些资源.如果使用Python 2.7.5运行,则如果需要Python 2.7及更高版本,则资源有效.所以检查必须多于一个相等的值.这在自定义配置文件中指定为单个语句.每个程序都有一个特定的配置文件,它必须尽可能小才能使用.使用配置文件中的单个语句检查一个资源.

一般类约占代码的98%.特定资源和配置只占代码的2%左右.因此,添加要检查的新资源以及新程序的新配置文件非常容易.

这个子资源:

class R_Sys(r_base.R_Base):
    '''
    doc : http://docs.python.org/3/library/sys.html#module-sys

    sys.modules returns only a list of imported module

    statement :
    sys.modules['psutil'] #  may return false (installed but not imported
    but the statements :
    import psutil
    sys.modules['psutil'] # will return true, now psutil is imported
    '''

    allowed_names = ('modules', 'path', 'builtin_module_names', 'stdin')

    allowed_keys_in_dict_config = ('name',)
    allowed_operators = ("R_NONE", "=", 'installed')  # installed only for modules

    class_group = 'Sys'
    module_used = sys   


    def __init__(self, check_type, group, name):
        super(R_Sys, self).__init__(check_type, group, name)
Run Code Online (Sandbox Code Playgroud)

由此配置语句调用:

sc.analyse(r.R_Sys, c.ct('DETECT'), dict(name='path'))
Run Code Online (Sandbox Code Playgroud)

可以成功腌制.但是使用config语句:

sc.analyse(r.R_Sys, c.ct('DETECT'),
                     dict(name='modules', tuplename='unum') )  
Run Code Online (Sandbox Code Playgroud)

它失败.

这意味着我认为98%的主代码应该没问题,否则第一个语句也会失败.

子类中有类属性.这些都需要正常运行.并且在第一次调用时,转储执行得很好.我还没有加载.

Mik*_*rns 5

dill 有一些很好的酸洗诊断工具,其中最好的是酸洗痕迹(类似于你实施的).

让我们构建一个复杂的对象,并探索:

>>> import dill
>>> class Foo(object):
...   @classmethod
...   def bar(self, x):
...     return self.z + x
...   def baz(self, z):
...     self.z = z
...   z = 1
...   zap = lambda self, x: x + self.bar(x)
... 
>>> f = Foo()
>>> f.zap(3)
7
>>> f.baz(7)
>>> f.z 
7
Run Code Online (Sandbox Code Playgroud)

打开"泡菜痕迹":

>>> dill.detect.trace(True)
>>> _f = dill.dumps(f)
T2: <class '__main__.Foo'>
F2: <function _create_type at 0x10f94a668>
T1: <type 'type'>
F2: <function _load_type at 0x10f94a5f0>
T1: <type 'object'>
D2: <dict object at 0x10f96bb40>
Cm: <classmethod object at 0x10f9ad408>
T4: <type 'classmethod'>
F1: <function bar at 0x10f9aa9b0>
F2: <function _create_function at 0x10f94a6e0>
Co: <code object bar at 0x10f9a9130, file "<stdin>", line 2>
F2: <function _unmarshal at 0x10f94a578>
D1: <dict object at 0x10e8d6168>
D2: <dict object at 0x10f96b5c8>
F1: <function baz at 0x10f9aaa28>
Co: <code object baz at 0x10f9a9ab0, file "<stdin>", line 5>
D1: <dict object at 0x10e8d6168>
D2: <dict object at 0x10f969d70>
F1: <function <lambda> at 0x10f9aaaa0>
Co: <code object <lambda> at 0x10f9a9c30, file "<stdin>", line 8>
D1: <dict object at 0x10e8d6168>
D2: <dict object at 0x10f97d050>
D2: <dict object at 0x10e97b4b0>
>>> f_ = dill.loads(_f)
>>> f_.z
7
Run Code Online (Sandbox Code Playgroud)

好的,dill可以腌制这个物体......所以让它变得更难.我们首先关闭跟踪.

>>> dill.detect.trace(False)
>>> 
>>> f.y = xrange(5)
>>> f.w = iter([1,2,3])
>>> 
>>> dill.pickles(f)
False
Run Code Online (Sandbox Code Playgroud)

好的,现在dill失败了.那导致失败的原因是什么?如果我们深入挖掘对象,我们可以查看所有未能腌制的对象f.

>>> dill.detect.badtypes(f)
<class '__main__.Foo'>
>>> dill.detect.badtypes(f, depth=1)
{'__hash__': <type 'method-wrapper'>, '__setattr__': <type 'method-wrapper'>, '__reduce_ex__': <type 'builtin_function_or_method'>, 'baz': <type 'instancemethod'>, '__reduce__': <type 'builtin_function_or_method'>, '__str__': <type 'method-wrapper'>, '__format__': <type 'builtin_function_or_method'>, '__getattribute__': <type 'method-wrapper'>, 'zap': <type 'instancemethod'>, '__delattr__': <type 'method-wrapper'>, '__repr__': <type 'method-wrapper'>, 'w': <type 'listiterator'>, '__dict__': <type 'dict'>, '__sizeof__': <type 'builtin_function_or_method'>, '__init__': <type 'method-wrapper'>}
>>> dill.detect.badobjects(f, depth=1)
{'__hash__': <method-wrapper '__hash__' of Foo object at 0x10f9b0050>, '__setattr__': <method-wrapper '__setattr__' of Foo object at 0x10f9b0050>, '__reduce_ex__': <built-in method __reduce_ex__ of Foo object at 0x10f9b0050>, 'baz': <bound method Foo.baz of <__main__.Foo object at 0x10f9b0050>>, '__reduce__': <built-in method __reduce__ of Foo object at 0x10f9b0050>, '__str__': <method-wrapper '__str__' of Foo object at 0x10f9b0050>, '__format__': <built-in method __format__ of Foo object at 0x10f9b0050>, '__getattribute__': <method-wrapper '__getattribute__' of Foo object at 0x10f9b0050>, 'zap': <bound method Foo.<lambda> of <__main__.Foo object at 0x10f9b0050>>, '__delattr__': <method-wrapper '__delattr__' of Foo object at 0x10f9b0050>, '__repr__': <method-wrapper '__repr__' of Foo object at 0x10f9b0050>, 'w': <listiterator object at 0x10f9b0550>, '__dict__': {'y': xrange(5), 'z': 7, 'w': <listiterator object at 0x10f9b0550>}, '__sizeof__': <built-in method __sizeof__ of Foo object at 0x10f9b0050>, '__init__': <method-wrapper '__init__' of Foo object at 0x10f9b0050>}
Run Code Online (Sandbox Code Playgroud)

嗯.好多啊.当然,并非所有这些对象都必须为我们的对象序列化序列化...但是至少其中一个对象导致失败.

自然要做的就是看看我们遇到的失败......那么,会抛出什么错误?也许那会暗示一下.

>>> dill.detect.errors(f)
PicklingError("Can't pickle <type 'listiterator'>: it's not found as __builtin__.listiterator",)
Run Code Online (Sandbox Code Playgroud)

啊哈,这listiterator是一个坏对象.让我们通过重新开启"追踪"来深入挖掘.

>>> dill.detect.trace(True)
>>> dill.pickles(f)
T2: <class '__main__.Foo'>
F2: <function _create_type at 0x10f94a668>
T1: <type 'type'>
F2: <function _load_type at 0x10f94a5f0>
T1: <type 'object'>
D2: <dict object at 0x10f9826e0>
Cm: <classmethod object at 0x10f9ad408>
T4: <type 'classmethod'>
F1: <function bar at 0x10f9aa9b0>
F2: <function _create_function at 0x10f94a6e0>
Co: <code object bar at 0x10f9a9130, file "<stdin>", line 2>
F2: <function _unmarshal at 0x10f94a578>
D1: <dict object at 0x10e8d6168>
D2: <dict object at 0x10f96b5c8>
F1: <function baz at 0x10f9aaa28>
Co: <code object baz at 0x10f9a9ab0, file "<stdin>", line 5>
D1: <dict object at 0x10e8d6168>
D2: <dict object at 0x10f969d70>
F1: <function <lambda> at 0x10f9aaaa0>
Co: <code object <lambda> at 0x10f9a9c30, file "<stdin>", line 8>
D1: <dict object at 0x10e8d6168>
D2: <dict object at 0x10f97d050>
D2: <dict object at 0x10e97b4b0>
Si: xrange(5)
F2: <function _eval_repr at 0x10f94acf8>
T4: <type 'listiterator'>
False
Run Code Online (Sandbox Code Playgroud)

实际上,它停在了listiterator.然而,请注意(正好在上面)xrange确实是泡菜.所以,让我们更换iterxrange

>>> f.w = xrange(1,4)  
>>> dill.detect.trace(False)
>>> dill.pickles(f)
True
>>> 
Run Code Online (Sandbox Code Playgroud)

我们的对象现在再次发酵.

dill 内置了一堆其他pickle检测工具,包括跟踪哪些对象指向哪些方法(用于调试递归酸洗失败).

我相信cloudpickle也有一些类似的工具来dill进行泡菜调试......但是在任何一种情况下的主要工具都与你构建的类似.