Pickle динамически параметризованный подкласс - PullRequest
14 голосов
/ 10 января 2011

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

Я хочу иметь возможность сохранять динамически параметризованные классы таким же образом, но не могу, потому что при попытке засолки я получаю 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's not found as __main__.AutoSubClass
import pickle
print pickle.dumps(cls)

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

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

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

Ответы [ 4 ]

8 голосов
/ 17 июля 2012

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

Используя __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__ документы парураз, чтобы точно увидеть, что здесь происходит.

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

5 голосов
/ 10 января 2011

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

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

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

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

Установка атрибута __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 голосов
/ 10 января 2011

Полагаю, уже слишком поздно, но 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

Но да, вы, вероятно, слишком усложняете вещи.

1 голос
/ 10 января 2011

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

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

...