我有一个通常存储腌制类类型的系统。
我希望能够以相同的方式保存动态参数化的类,但我不能,因为我试图挑选一个未全局找到的类(未在简单代码中定义)时得到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
)。然后,通过保存“ParameterizableBaseClass”类型和不同的param值(上面的动态cls
),可以选择动态参数化子类(上面的param_value
)。
我确信在很多情况下,这可以完全避免......我可以在我的代码中避免这种情况,如果我真的(真的)必须这样做的话。我在某个时候玩__metaclass__
,copyreg
甚至是__builtin__.issubclass
(不要问),但是无法破解这个。
如果我不问的话,我觉得我不会忠于蟒蛇精神:如何以相对干净的方式实现这一目标?
答案 0 :(得分: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)
值得一读__reduce__
docs几次,看看到底发生了什么。
希望有人觉得这很有用。
答案 1 :(得分:5)
是的,有可能 -
每当您想要为对象自定义Pickle和Unpickle行为时,您只需在类本身上设置“__getstate__
”和“__setstate__
”方法。
在这种情况下,它有点棘手: 正如您所观察到的那样,需要在全局命名空间上存在一个类,该类是当前被pickle对象的类:它必须是同一个类,具有相同的名称。好的 - 交易是这个全局名称空间中存在的类可以在Pickle时创建。
在Unpickle时间,具有相同名称的类必须存在 - 但它不必是同一个对象 - 只是表现得像 - 并且在Unpickling过程中调用__setstate__
,它可以重新创建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)
答案 2 :(得分: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
但是,你可能过于复杂了。
答案 3 :(得分:1)
无法在模块的顶层创建的类as shown in the Python documentation。
此外,即使对于顶级模块类的实例,也不存储类属性。因此,在您的示例中,PARAM
无论如何都不会存储。 (在上面链接的Python文档部分中进行了解释)