Изящно деградирующее травление в Python

(Вы можете прочитать этот вопрос для некоторого фона)

Я хотел бы иметь способ изящно деградировать объекты в Python.

При мариновании объекта, назовем его основным объектом, иногда Pickler вызывает исключение, потому что он не может мариновать определенный подобъект основного объекта. Например, я часто получаю сообщение об ошибке «не могу замариновать объекты модуля». Это потому, что я ссылаюсь на модуль из основного объекта.

Я знаю, что могу написать кое-что, чтобы заменить этот модуль фасадом, который будет содержать атрибуты модуля, но это будет иметь свои проблемы (1).

Так что я бы хотел, чтобы функция травления автоматически заменяла модули (и любые другие труднодоступные для травления объекты) фасадами, содержащими их атрибуты. Это может не дать идеального травления, но во многих случаях этого будет достаточно.

Есть ли что-нибудь подобное? Кто-нибудь знает, как к этому подойти?


(1) Одной из проблем может быть то, что модуль может ссылаться на другие модули внутри себя.


person Ram Rachum    schedule 28.08.2009    source источник
comment
Java Beans .. Python Pickles .. Я хотел бы задушить кретинов, которые придумывают эти милые штуки   -  person Ben M    schedule 28.08.2009


Ответы (3)


Вы можете решить и реализовать, как любой ранее неподдающийся анализу тип будет обрабатываться и распаковываться: см. модуль стандартной библиотеки copy_reg (переименован в copyreg в Python 3.*).

По сути, вам нужно предоставить функцию, которая, учитывая экземпляр типа, сводит его к кортежу — с тем же протоколом, что и reduce специальный метод (за исключением того, что специальный метод сокращения не принимает аргументов, поскольку, когда он предоставляется, он вызывается непосредственно для объекта, в то время как функция вы предоставите объект в качестве единственного аргумента).

Как правило, кортеж, который вы возвращаете, имеет 2 элемента: вызываемый объект и кортеж аргументов для передачи. Вызываемый объект должен быть зарегистрирован как «безопасный конструктор» или, что то же самое, иметь атрибут __safe_for_unpickling__ со значением true. Эти элементы будут обработаны, и во время распаковки вызываемый объект будет вызываться с заданными аргументами и должен возвращать невыбранный объект.

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

>>> import sys, pickle, copy_reg
>>> def savemodule(module):
...   return __import__, (module.__name__,)
... 
>>> copy_reg.pickle(type(sys), savemodule)
>>> s = pickle.dumps(sys)
>>> s
"c__builtin__\n__import__\np0\n(S'sys'\np1\ntp2\nRp3\n."
>>> z = pickle.loads(s)
>>> z
<module 'sys' (built-in)>

Я использую старомодную ASCII-форму команды pickle, чтобы s, строку, содержащую команду pickle, было легко проверить: она указывает распаковке вызвать встроенную функцию импорта со строкой sys в качестве единственного аргумента. И z показывает, что это действительно возвращает нам встроенный sys модуль в результате распаковки, как и хотелось.

Теперь вам придется сделать вещи немного сложнее, чем просто __import__ (вам придется иметь дело с сохранением и восстановлением динамических изменений, навигацией по вложенному пространству имен и т. д.), и поэтому вам также придется вызывать copy_reg.constructor (передавая в качестве аргумента вашей собственной функции, которая выполняет эту работу) перед тем, как вы copy_reg сохраните модуль, который возвращает другую вашу функцию (и, если в отдельном запуске, также перед тем, как вы распарываете те огурцы, которые вы сделали, используя указанную функцию). Но я надеюсь, что эти простые случаи помогут показать, что на самом деле в этом нет ничего «внутренне» сложного!-)

person Alex Martelli    schedule 28.08.2009
comment
@Alex Martelli: Когда я использую copy_reg.pickle, в какой области это изменение будет актуальным? Я хочу, чтобы люди могли импортировать мою работу без изменения каких-либо системных значений, которые могут повредить их программу. - person Ram Rachum; 06.09.2009
comment
copy_reg является глобальным. Но если они в настоящее время не занимаются сбором модулей (что невозможно по умолчанию в системе), он не может разрушить их программу, чтобы сделать модули доступными для выбора. - person Alex Martelli; 07.09.2009
comment
@Alex Martelli: Но если бы они столкнулись с той же проблемой и определили травление модулей по-другому, у нас возникла бы проблема. Я верю в то, что нужно быть вежливым и не менять состояние системы. Я считаю, что когда вы импортируете какой-либо модуль в Python, вам не нужно беспокоиться о том, что он испортит глобальные переменные вашей системы, и что важно иметь инструменты, которые позволят вам избежать такого рода невежливости в ваших модулях. - person Ram Rachum; 07.09.2009
comment
@cool-RR, я понимаю вашу точку зрения, но не буду расширять ее дальше, чем проверка того, что модули не могут быть замаринованы (при запуске попробуйте/за исключением попытки замариновать модуль) и разрешить потенциальным сторонним повторным пользователям вашего код для включения и выключения вашей схемы. Разрешение повторного использования не должно происходить за счет вмешательства в чистое и поддерживаемое использование, когда маловероятно, что задокументированные и разработанные способы разрешения травления модулей вызовут плохой эффект, о котором вы беспокоитесь (и я НИКОГДА не видел НИ ОДНОЙ платформы, которая модули pickles... и я видел их ОЧЕНЬ МНОГО!-). - person Alex Martelli; 07.09.2009

Как насчет следующего, который представляет собой оболочку, которую вы можете использовать, чтобы обернуть некоторые модули (возможно, любой модуль) во что-то, что можно распарить. Затем вы можете подклассировать объект Pickler, чтобы проверить, является ли целевой объект модулем, и если да, обернуть его. Осуществляет ли это то, что вы желаете?

class PickleableModuleWrapper(object):
    def __init__(self, module):
        # make a copy of the module's namespace in this instance
        self.__dict__ = dict(module.__dict__)
        # remove anything that's going to give us trouble during pickling
        self.remove_unpickleable_attributes()

    def remove_unpickleable_attributes(self):
        for name, value in self.__dict__.items():
            try:
                pickle.dumps(value)
            except Exception:
                del self.__dict__[name]

import pickle
p = pickle.dumps(PickleableModuleWrapper(pickle))
wrapped_mod = pickle.loads(p)
person Jason R. Coombs    schedule 28.08.2009

Хм, что-то вроде этого?

import sys

attribList = dir(someobject)
for attrib in attribList:
    if(type(attrib) == type(sys)): #is a module
        #put in a facade, either recursively list the module and do the same thing, or just put in something like str('modulename_module')
    else:
        #proceed with normal pickle

Очевидно, это вошло бы в расширение класса pickle с перереализованным методом дампа...

person Chinmay Kanchi    schedule 28.08.2009