【问题标题】:Pickle a dynamically parameterized sub-class腌制一个动态参数化的子类
【发布时间】:2011-06-06 13:29:54
【问题描述】:

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

我希望能够以相同的方式保存动态参数化的类,但我不能,因为我在尝试腌制一个未全局找到的类(未在简单代码中定义)时收到 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(不要问),但无法破解这个。

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

【问题讨论】:

  • 以相对干净的方式?我认为“干净”的任何合理定义都不可能。您为什么要尝试腌制其类在运行时生成的对象?实际用例是什么?你将如何解开那些类不存在的东西?
  • 我也不明白这个用例。为什么你不能在初始化后只返回一个带有该参数的类的实例作为属性。另一条评论,您还应该避免使用 python 魔法,即self.__class__.PARAM。我认为你过于复杂了。
  • 这可能是过于复杂的情况(在这种情况下,问题可能仍然存在,即使不考虑我的情况)。我的例子可能看起来很愚蠢——这只是一个例子。我的真实系统将类类型与创建参数一起腌制,并在并行处理场景中的进程之间传递它们。除了使用大约 20 个不同的类,每个类都有特定的代码,我还有一些“只需选择你的参数”类。我希望能够动态地对它们进行子类化,而无需更改(并可能重新加载)代码,并使它们可腌制。 HTH。
  • @Lennart:“你要如何解开那些类不存在的东西?” - 通过在 unpickle 时间创建课程! :-) - 请参阅下面的答案。
  • @jsbueno:我不得不说,在那种情况下,感觉就像整个动态类创建可以被跳过,它仍然可以工作。你知道名字,参数是动态的吗?好吧,您不需要为此创建一个类...

标签: python class dynamic factory pickle


【解决方案1】:

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

使用__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,看看这里到底发生了什么。

希望有人觉得这很有用。

【讨论】:

    【解决方案2】:

    是的,有可能-

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

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

    在 Unpickle 时,同名的类必须存在 - 但它不必是同一个对象 - 就像它一样 - 并且在 Unpickling 过程中调用 __setstate__ 时,它可以重新创建原始对象的参数化类,并通过设置对象的__class__属性将其自己的类设置为该类。

    设置对象的__class__ 属性可能看起来令人反感,但这是 OO 在 Python 中的工作方式,并且它已被正式记录,它甚至可以跨实现工作。 (我在 Python 2.6 和 Pypy 中都测试了这个 sn-p)

    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)
    

    【讨论】:

    • 我想我只能怪我自己,我试图使用的伎俩充其量是骇人听闻的......你的方法似乎是实现所讨论内容的方法。
    • 这里的诀窍是你仍然提供一个全局类定义,如果只是一个虚拟的:class AutoSub(Base): pass。但是很好。
    【解决方案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
    

    但是,是的,您可能过于复杂了。

    【讨论】:

    • 考虑到在其他模块中被腌制的此类实例,当您尝试腌制cls 的实例时,它可能会起作用,因为您可以确定类工厂已经运行。但我不太确定此类实例(在其他模块中)的 unpickling,因为在这种情况下,globals()[clsname] 分配可能没有运行(想想下一次运行程序时,它想读回被腌制的实例)。
    【解决方案4】:

    没有在模块顶层创建的类不能被pickle,as shown in the Python documentation

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

    【讨论】:

    • 感谢您的回复!确实,随意使用pickle.dump 不会同意腌制我的班级类型。我正在寻找一种机制(例如__reduce____setstate__ 或某些基类或元类构造中的类似物),它可以让我腌制一个元组描述我的类,并且稍后重建我的课程。
    猜你喜欢
    • 2021-12-18
    • 1970-01-01
    • 1970-01-01
    • 2010-12-29
    • 1970-01-01
    • 1970-01-01
    • 2014-01-16
    • 2010-12-27
    • 1970-01-01
    相关资源
    最近更新 更多