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

Рассортировать динамически параметризованный подкласс

У меня есть система, которая обычно хранит маринованные типы классов.

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

Моя проблема может быть смоделирована как следующий пример кода:

class Base(object):
 def m(self):
  return self.__class__.PARAM

def make_parameterized(param_value):
 class AutoSubClass(Base):
  PARAM = param_value
 return AutoSubClass

cls = make_parameterized(input("param value?"))

Когда я пытаюсь разбор класса, я получаю следующую ошибку:

# pickle.PicklingError: Can't pickle <class '__main__.AutoSubClass'>: it not found as __main__.AutoSubClass
import pickle
print pickle.dumps(cls)

Я ищу некоторый метод для объявления Base как ParameterizableBaseClass, который должен определить необходимые параметры (PARAM в приведенном выше примере). Затем динамический параметризованный подкласс (cls выше) должен быть подбираем, сохраняя тип "ParameterizableBaseClass" и различные значения параметров (динамический param_value выше).

Я уверен, что во многих случаях этого можно избежать вообще... И я могу избежать этого и в своем коде, если я действительно (действительно) должен. Я играл с __metaclass__, copyreg и даже __builtin__.issubclass в какой-то момент (не спрашиваю), но не смог взломать этот.

Я чувствую, что не верю в дух питона, если не буду спрашивать: как это может быть достигнуто относительно чистым способом?

4b9b3361

Ответ 1

Да, возможно -

Всякий раз, когда вы хотите настроить поведение Pickle и Unpickle для своих объектов, вам просто нужно установить методы "__getstate__" и "__setstate__" в самом классе.

В этом случае это немного сложнее: Там, как вы заметили, нужно, чтобы существовал класс в глобальном пространстве имен, который является классом в настоящее время маринованного объекта: он должен быть одним и тем же классом с тем же именем. Хорошо - дело в том, что класс gthis, существующий в пространстве глобальных имен, может быть создан во время Pickle.

В разное время класс с таким же именем должен существовать - но он не должен быть одним и тем же объектом - просто вести себя так, как он есть, - и, поскольку __setstate__ вызывается в Unpickling proccess, он может воссоздать параметризованный класс объекта orignal и установить его собственный класс таким, чтобы установить атрибут __class__ объекта.

Установка атрибута __class__ объекта может показаться неприемлемым, но именно так работает OO в Python и оно официально задокументировано, оно даже работает по сравнению с реализациями. (Я тестировал этот фрагмент как в Python 2.6, так и в Pypy)

class Base(object):
    def m(self):
        return self.__class__.PARAM
    def __getstate__(self):
        global AutoSub
        AutoSub = self.__class__
        return (self.__dict__,self.__class__.PARAM)
    def __setstate__(self, state):
        self.__class__ = make_parameterized(state[1])
        self.__dict__.update(state[0])

def make_parameterized(param_value):
    class AutoSub(Base):
        PARAM = param_value
    return AutoSub

class AutoSub(Base):
    pass


if __name__ == "__main__":

    from pickle import dumps, loads

    a = make_parameterized("a")()
    b = make_parameterized("b")()

    print a.PARAM, b.PARAM, type(a) is type(b)
    a_p = dumps(a)
    b_p = dumps(b)

    del a, b
    a = loads(a_p)
    b = loads(b_p)

    print a.PARAM, b.PARAM, type(a) is type(b)

Ответ 2

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

Используя метод __reduce__, мы можем предоставить вызываемый, который вернет неинициализированный экземпляр нашего желаемого класса.

class Base(object):
    def m(self):
        return self.__class__.PARAM

    def __reduce__(self):
        return (_InitializeParameterized(), (self.PARAM, ), self.__dict__)


def make_parameterized(param_value):
    class AutoSub(Base):
        PARAM = param_value
    return AutoSub


class _InitializeParameterized(object):
    """
    When called with the param value as the only argument, returns an 
    un-initialized instance of the parameterized class. Subsequent __setstate__
    will be called by pickle.
    """
    def __call__(self, param_value):
        # make a simple object which has no complex __init__ (this one will do)
        obj = _InitializeParameterized()
        obj.__class__ = make_parameterized(param_value)
        return obj

if __name__ == "__main__":

    from pickle import dumps, loads

    a = make_parameterized("a")()
    b = make_parameterized("b")()

    print a.PARAM, b.PARAM, type(a) is type(b)
    a_p = dumps(a)
    b_p = dumps(b)

    del a, b
    a = loads(a_p)
    b = loads(b_p)

    print a.PARAM, b.PARAM, type(a) is type(b)

Стоит прочитать __reduce__ docs пару раз, чтобы точно увидеть, что здесь происходит.

Надеюсь, что кто-то найдет это полезным.

Ответ 3

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

В любом случае, поскольку pickle хочет класс в глобальном, он может иметь его:

import cPickle

class Base(object):
    def m(self):
        return self.__class__.PARAM

    @classmethod
    def make_parameterized(cls,param):
        clsname = "AutoSubClass.%s" % param
        # create a class, assign it as a global under the same name
        typ = globals()[clsname] = type(clsname, (cls,), dict(PARAM=param))
        return typ

cls = Base.make_parameterized('asd')

import pickle
s = pickle.dumps(cls)

cls = pickle.loads(s)
print cls, cls.PARAM
# <class '__main__.AutoSubClass.asd'> asd

Но да, вы, вероятно, слишком оскорбительны.

Ответ 4

Классы, которые не созданы на верхнем уровне модуля, не могут быть маринованными, как показано в документации Python.

Кроме того, даже для экземпляра класса модуля верхнего уровня атрибуты класса не сохраняются. Поэтому в вашем примере PARAM не будет храниться в любом случае. (Объясняется также в разделе документации документации Python)