Pickle 一个动态参数化的子类

问题描述 投票:0回答:5

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

我希望能够以相同的方式保存动态参数化的类,但我不能,因为我在尝试 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?"))

当我尝试 pickle 课程时,出现以下错误:

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

我正在寻找一些方法来将 Base 声明为

ParameterizableBaseClass
,它应该定义所需的参数(上例中的
PARAM
)。然后应该通过保存“ParameterizableBaseClass”类型和不同的参数值(上面的动态
cls
)来选择动态参数化子类(上面的
param_value
)。

我确信在很多情况下,这可以完全避免……如果我真的(真的)不得不这样做,我也可以在我的代码中避免这种情况。我在玩

__metaclass__
copyreg
甚至
__builtin__.issubclass
在某些时候(不要问),但无法破解这个。

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

python class dynamic factory pickle
5个回答
14
投票

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

使用

__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
投票

是的,有可能-

每当您想为对象自定义 Pickle 和 Unpickle 行为时,只需在类本身上设置“

__getstate__
”和“
__setstate__
”方法即可。

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

在 Unpickle 时,必须存在具有相同名称的类 - 但它不必是同一个对象 - 就像它一样 - 并且在 Unpickling 过程中调用

__setstate__
时,它可以重新创建参数化原始对象的类,并通过设置对象的
__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
投票

我想现在已经太晚了,但是 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
投票

不在模块顶层创建的类不能被腌制,如 Python 文档中所示.

此外,即使对于顶级模块类的实例,也不会存储类属性。所以在你的例子中

PARAM
无论如何都不会被存储。 (在上面链接的 Python 文档部分也有解释)


0
投票

可以通过自定义元类并在其上使用

copyreg
。这样,您就可以 pickle 任何自定义的动态参数化子类。 这在 issue 7689 中进行了描述和实现。

示范:

import pickle
import copyreg


class Metaclass(type):
    """
    __getstate__ and __reduce__ do not work.
    However, we can register this via copyreg. See below.
    """


class Base:
    """Some base class. Does not really matter, you could also use `object`."""


def create_cls(name):
    return Metaclass(name, (Base,), {})


cls = create_cls("MyCustomObj")
print(f"{cls=}")


def _reduce_metaclass(cls):
    metaclass = cls.__class__
    cls_vars = dict(vars(cls))
    cls_vars.pop("__dict__", None)
    cls_vars.pop("__weakref__", None)
    print("reduce metaclass", cls, metaclass, cls.__name__, cls.__bases__, vars(cls))
    return metaclass, (cls.__name__, cls.__bases__, cls_vars)


copyreg.pickle(Metaclass, _reduce_metaclass)


cls = pickle.loads(pickle.dumps(cls))
print(f"{cls=} after pickling")

a = cls()
print(f"instance {a=}, {a.__class__=}, {a.__class__.__mro__=}")
a = pickle.loads(pickle.dumps(a))
print(f"instance {a=} after pickling, {a.__class__=}, {a.__class__.__mro__=}")
© www.soinside.com 2019 - 2024. All rights reserved.