Pickle 动态参数化子类

发布于 2024-10-11 09:21:52 字数 1006 浏览 8 评论 0原文

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

我希望能够以相同的方式保存动态参数化的类,但我不能,因为我在尝试腌制一个未全局找到的类(未在简单代码中定义)时收到 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 声明为 ParameterizedBaseClass ,该方法应定义所需的参数(上例中的PARAM)。然后,动态参数化子类(上面的cls)应该可以通过保存“ParameterizedBaseClass”类型和不同的参数值(上面的动态param_value)来选择。

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

我觉得如果我不问:如何以相对干净的方式实现这一点,我就不会真正忠于Python精神。

I have a system which commonly stores pickled class types.

I want to be able to save dynamically-parameterized classes in the same way, but I can't because I get a PicklingError on trying to pickle a class which is not globally found (not defined in simple code).

My problem can be modeled as the following example code:

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?"))

When I try to pickle the class, I get the following error:

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

I am looking for some method to declare Base as a ParameterizableBaseClass which should define the params needed (PARAM in above example). A dynamic parameterized subclass (cls above) should then be picklable by saving the "ParameterizableBaseClass" type and the different param-values (dynamic param_value above).

I am sure that in many cases, this can be avoided altogether... And I can avoid this in my code as well if I really (really) have to. I was playing with __metaclass__, copyreg and even __builtin__.issubclass at some point (don't ask), but was unable to crack this one.

I feel like I wouldn't be true to the python spirit if I wasn't to ask: how can this be achieved, in a relatively clean way?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

大海や 2024-10-18 09:21:52

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

使用 __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__ 文档几次才能确切地了解这里发生了什么。

希望有人觉得这很有用。

I know this is a very old question, but I think it is worth sharing a better means of pickling the parameterised classes than the one that is the currently accepted solution (making the parameterised class a global).

Using the __reduce__ method, we can provide a callable which will return an uninitialised instance of our desired class.

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)

It is worth reading the __reduce__ docs a couple of times to see exactly what is going on here.

Hope somebody finds this useful.

女中豪杰 2024-10-18 09:21:52

是的,这是可能的 -

每当您想要为对象自定义 Pickle 和 Unpickle 行为时,您只需在对象上设置“__getstate__”和“__setstate__”方法即可。类本身。

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

在 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)

Yes, it is possible -

Whenever you want to custom the Pickle and Unpickle behaviors for your objects, you just have to set the "__getstate__" and "__setstate__" methods on the class itself.

In this case it is a bit trickier:
There need, as you observed - to exist a class on the global namespace that is the class of the currently being pickled object: it has to be the same class, with the same name. Ok - the deal is that gthis class existing in the globalname space can be created at Pickle time.

At Unpickle time the class, with the same name, have to exist - but it does not have to be the same object - just behave like it does - and as __setstate__ is called in the Unpickling proccess, it can recreate the parameterized class of the orignal object, and set its own class to be that one, by setting the __class__ attribute of the object.

Setting the __class__ attribute of an object may seen objectionable but it is how OO works in Python and it is officially documented, it even works accross implementations. (I tested this snippet in both Python 2.6 and 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)
执手闯天涯 2024-10-18 09:21:52

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

但是,是的,你可能把事情变得过于复杂了。

I guess it's too late now, but pickle is a module I'd rather avoid for anything complex, because it has problems like this one and many more.

Anyways, since pickle wants the class in a global it can have it:

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

But yeah, you're probably overcomplicating things.

祁梦 2024-10-18 09:21:52

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

此外,即使对于顶级模块类的实例,也不会存储类属性。因此,在您的示例中 PARAM 无论如何都不会被存储。 (上面链接的 Python 文档部分也有解释)

Classes that are not created in the top level of a module cannot be pickled, as shown in the Python documentation.

Furthermore, even for an instance of a top level module class the class attributes are not stored. So in your example PARAM wouldn't be stored anyway. (Explained in the Python documentation section linked above as well)

开始看清了 2024-10-18 09:21:52

通过使用自定义元类并在其上使用copyreg 可以实现这一点。这样,您就可以pickle任何自定义动态参数化子类。
问题 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__=}")

It is possible by having a custom metaclass and using copyreg on it. That way, you can pickle any custom dynamically parameterized sub-classes.
This is described and implemented in issue 7689.

Demonstration:

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__=}")
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文