Pickle一个动态参数化的子类

Yon*_*tan 14 python factory class dynamic pickle

我有一个通常存储腌制类类型的系统.

我希望能够以相同的方式保存动态参数化的类,但我不能,因为我试图挑选一个非全局发现的类(未在简单代码中定义)得到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?"))
Run Code Online (Sandbox Code Playgroud)

当我尝试挑选该类时,我收到以下错误:

# pickle.PicklingError: Can't pickle <class '__main__.AutoSubClass'>: it's not found as __main__.AutoSubClass
import pickle
print pickle.dumps(cls)
Run Code Online (Sandbox Code Playgroud)

我正在寻找一些方法来声明Base作为一个ParameterizableBaseClass应该定义所需的params(PARAM在上面的例子中).cls然后,通过保存"ParameterizableBaseClass"类型和不同的param值(param_value上面的动态),可以选择动态参数化子类(上面).

我相信在很多情况下,这可以完全避免......我可以在我的代码中避免这种情况,如果我真的(真的)必须这样做的话.我正在玩__metaclass__,copyreg甚至__builtin__.issubclass在某些时候(不要问),但是无法破解这个.

如果我不问的话,我觉得我不会忠于蟒蛇精神:如何以相对干净的方式实现这一目标?

pel*_*son 8

我知道这是一个非常古老的问题,但我认为值得共享一种更好的方法来挑选参数化类,而不是当前接受的解决方案(使参数化类成为全局).

使用该__reduce__方法,我们可以提供一个callable,它将返回我们所需类的未初始化实例.

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)
Run Code Online (Sandbox Code Playgroud)

值得一读这些__reduce__文档,以确切了解这里发生了什么.

希望有人觉得这很有用.


jsb*_*eno 5

对的,这是可能的 -

每当您想为对象自定义Pickle和Unpickle行为时,您只需在类本身上设置" __getstate__"和" __setstate__"方法.

在这种情况下,它有点棘手:正如您所观察到的那样,需要在全局命名空间上存在一个类,该类是当前被pickle对象的类:它必须是同一个类,具有相同的名称.好的 - 交易是这个全局名称空间中存在的类可以在Pickle时创建.

在Unpickle时,具有相同名称的类必须存在 - 但它不必是相同的对象 - 只是表现得像 - 并且__setstate__在Unpickling过程中调用它,它可以重新创建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)
Run Code Online (Sandbox Code Playgroud)