Подтвердить что ты не робот

Как проверить, какая деталь сложного объекта не может быть маринована

Обзор

Я хочу сериализовать сложные объекты. Это выглядит просто, но каждый шаг создает другую проблему.

В конце концов, другие программисты также должны иметь возможность создавать сложный объект, унаследованный от моего родительского объекта. И этот объект должен быть разборчивым, для Python 2.7 и Python3.x.

Я начал с простого объекта и успешно использовал pickle.dump и pickle.load.

Затем я создал несколько сложных объектов (похожих, но не идентичных), некоторые из которых можно сбрасывать, а некоторые не могут.

Отладка

Библиотека рассола знает, какие объекты можно мариновать или нет. Теоретически это означает, что pdb может быть настроен для включения отладки pickle.

Альтернативные библиотеки сериализации

Мне нужна надежная сериализация, независимая от содержимого объекта. Поэтому я искал другие инструменты для сериализации:

  • Cerealizer, который selftest не удался и, кажется, устарел.
  • MessagePack, который недоступен для Python 3.
  • Я попробовал JSON и получил ошибку: builtins.TypeError: <lib.scan.Content object at 0x7f37f1e5da50> is not JSON serializable
  • Я посмотрел на Маршала и Шелли, но все они относятся к Пике.

Копаем в использование рассола

Я прочитал Как проверить, является ли объект разборчивым, который не дал мне ответа.

Самое близкое, что я нашел, было Как найти источник ошибки в 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))

Я вызываю этот код, используя:

def save(obj, file):  
    if platform.python_implementation() == 'CPython':
        myPickler = MyPickler(file)                
        myPickler.save(obj) 

Я ожидаю, что сохранение будет выполнено до тех пор, пока не будет создано исключение. Содержимое obj напечатано, чтобы я мог точно видеть, где ошибка orcurs. Но результат:

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'>
...

Это лишь небольшая часть результата. Я этого не понимаю. Это не помогает мне, какая деталь неправа для рассола. И как это решить.

Я видел: http://docs.python.org/3/library/pickle.html#what-can-be-pickled-and-unpickled, но это не очень помогает, если я не могу определить, какая строка в моем коде не может быть маринована.

Код в моем сложном объекте работает как ожидающий, в конце работает сгенерированный код как:

sys.modules['unum']

Но при травлении кажется, что "модуль" не читается, как ожидалось.

Попытка решения

Некоторые предпосылки, чтобы понять, что я имею в виду. У меня были программы, которые работали, и внезапно не сработали. Это может быть обновление или другой ресурс изменений. Программы, которые работают для других, а не для меня и наоборот.

Это общая проблема, поэтому я хочу разработать программу для проверки всех видов ресурсов. Количество различных видов ресурсов огромно. Поэтому у меня есть один класс родительских объектов со всем общим поведением. И как минимум, возможный класс подробностей для конкретных ресурсов.

Это выполняется в моих дочерних классах ресурсов.

Эти ресурсы должны быть проверены с различными версиями f.e. 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)

вызываемый этим оператором config:

sc.analyse(r.R_Sys, c.ct('DETECT'), dict(name='path'))

может быть успешно протравлено. Но с командой config:

sc.analyse(r.R_Sys, c.ct('DETECT'),
                     dict(name='modules', tuplename='unum') )  

он не работает.

Это означает, что, по моему мнению, 98% основной код должен быть в порядке, иначе первый оператор также потерпит неудачу.

В дочернем классе есть атрибуты класса. Они должны функционировать должным образом. И снова в первом вызове дамп выполняется хорошо. Я еще не делал нагрузки.

4b9b3361

Ответ 1

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

Включить трассировку рассола:

>>> 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

Хорошо, dill может разжечь этот объект... так что сделайте его сложнее. Сначала отключите трассировку.

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

Хорошо, теперь 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>}

Хммм. Это много. Конечно, не все эти объекты должны сериализоваться для нашего объекта для сериализации... однако по крайней мере один из них вызывает сбой.

Естественное дело - посмотреть на неудачу, которую мы получаем... Итак, какая ошибка, которая будет выброшена? Может быть, это даст подсказку.

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

Aha, 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

Действительно, он останавливается на listiterator. Однако заметьте (только что выше), что xrange делает рассол. Итак, заменим iter на xrange

>>> f.w = xrange(1,4)  
>>> dill.detect.trace(False)
>>> dill.pickles(f)
True
>>> 

Наш объект снова рассоривается.

dill содержит кучу других инструментов обнаружения рассола, в том числе методы отслеживания того, на какие объекты указывает (полезно для отладки рекурсивных травильных сбоев).

Я считаю, что cloudpickle также имеет некоторые аналогичные инструменты для dill для отладки pickle... но основной инструмент в любом случае похож на то, что вы создали.