【发布时间】:2012-08-04 08:09:01
【问题描述】:
假设我在一个模块中有这个 sn-p
def func(params):
class MyClass(object):
pass
如何腌制 MyClass 类的实例?
【问题讨论】:
-
为什么不把你的类放到模块范围内?
假设我在一个模块中有这个 sn-p
def func(params):
class MyClass(object):
pass
如何腌制 MyClass 类的实例?
【问题讨论】:
你不能,因为 picklable 对象的类定义必须驻留在导入模块的范围内。只需将您的课程放在模块范围内,您就可以开始了。
也就是说,在 Python 中,通过对机器内部(在本例中为 sys.modules)进行一些黑客攻击是无法实现的,但我不建议这样做。
【讨论】:
MyClass 定义是func 函数的局部变量。您不能直接创建它的实例,但可以将其功能映射到新类,然后像原来的那样使用新类。这是一个例子:
def func(params):
class MyClass(object):
some_param = 100
def __init__(self, *args):
print "args:", args
def blabla(self):
self.x = 123
print self.some_param
def getme(self):
print self.x
func.func_code是func函数的代码,func.func_code.co_consts[2]包含MyClass定义的字节码:
In : func.func_code.co_consts
Out:
(None,
'MyClass',
<code object MyClass at 0x164dcb0, file "<ipython-input-35-f53bebe124be>", line 2>)
所以我们需要MyClass 函数的字节码:
In : eval(func.func_code.co_consts[2])
Out:
{'blabla': <function blabla at 0x24689b0>,
'__module__': '__main__',
'getme': <function getme at 0x2468938>,
'some_param': 100,
'__init__': <function __init__ at 0x219e398>}
最后我们用元类创建一个新类,将 MyClass 函数分配给新类:
def map_functions(name, bases, dict):
dict.update(eval(func.func_code.co_consts[2]))
return type(name, bases, dict)
class NewMyClass(object):
__metaclass__ = map_functions
n = NewMyClass(1, 2, 3, 4, 5)
>> args: (1, 2, 3, 4, 5)
n.blabla()
>> 100
n.getme()
>> 123
【讨论】:
这有点难做,因为默认情况下,Pickle 对用户定义类中的对象所做的方式是创建该类的新实例 - 使用对象的 __class__.__name__ 属性在对象的原始模块中检索其类型。这意味着:pickling 和 unpickling 仅适用于(默认情况下)在它们定义的模块中具有明确定义的名称的类。
当在函数内部定义一个类时,通常不会有一个模块级(即全局)变量保存函数内部创建的每个类的名称。
pickle 和 npickle 的行为可以通过类上的 __getstate__ 和 __setstate__ 方法进行自定义 - 检查 docs - 但即使是他们,为动态类做正确的事情也可能很棘手,但我设法做到了为另一个 S.O. 创建它的工作实现。问题--在这里检查我的答案:
Pickle a dynamically parameterized sub-class
【讨论】:
您可以通过将类定义作为字符串包含在为实例腌制的数据中以及 exec()通过添加传递类的 __reduce__() 方法自行取消腌制时自行解决腌制要求,即类定义可导入可调用的定义。这是一个简单的例子来说明我的意思:
from textwrap import dedent
# Scaffolding
definition = dedent('''
class MyClass(object):
def __init__(self, attribute):
self.attribute = attribute
def __repr__(self):
return '{}({!r})'.format(self.__class__.__name__, self.attribute)
def __reduce__(self):
return instantiator, (definition, self.attribute)
''')
def instantiator(class_def, init_arg):
""" Create class and return an instance of it. """
exec(class_def)
TheClass = locals()['MyClass']
return TheClass(init_arg)
# Sample usage
import pickle
from io import BytesIO
stream = BytesIO() # use a memory-backed file for testing
obj = instantiator(definition, 'Foo') # create instance of class from definition
print('obj: {}'.format(obj))
pickle.dump(obj, stream)
stream.seek(0) # rewind
obj2 = pickle.load(stream)
print('obj2: {}'.format(obj2))
输出:
obj: MyClass('Foo')
obj2: MyClass('Foo')
显然,将类定义字符串包含在每个已腌制的类实例中效率低下,因此冗余可能使其不切实际,具体取决于所涉及的类实例的数量。
【讨论】: