我一直在试图获得一些动态创建的类型(即通过调用3-arg创建的类型type())来进行pickle和unpickle.我一直在使用这个模块切换技巧来隐藏模块用户的细节并给出干净的语义.
我已经学到了几件事:
getattr在模块本身上找到该类型getattr找到的一致,也就是说如果我们调用pickle.dumps(o)那么它必须是真的type(o) == getattr(module, 'name of type')虽然我被困住了,但似乎仍有一些奇怪的事情发生 - 它似乎在呼唤__getstate__一些意想不到的东西.
这是我用最简单的设置重现问题,使用Python 3.5进行测试,但如果可能的话我想回到3.3:
# module.py
import sys
import functools
def dump(self):
return b'Some data' # Dummy for testing
def undump(self, data):
print('Undump: %r' % data) # Do nothing for testing
# Cheaty demo way to make this consistent
@functools.lru_cache(maxsize=None)
def make_type(name):
return type(name, (), {
'__getstate__': dump,
'__setstate__': undump,
})
class Magic(object):
def __init__(self, path):
self.path = path
def __getattr__(self, name):
print('Getting thing: %s (from: %s)' % (name, self.path))
# for simple testing all calls to make_type must end in last x.y.z.last
if name != 'last':
if self.path:
return Magic(self.path + '.' + name)
else:
return Magic(name)
return make_type(self.path + '.' + name)
# Make the switch
sys.modules[__name__] = Magic('')
Run Code Online (Sandbox Code Playgroud)
然后快速练习:
import module
import pickle
f=module.foo.bar.woof.last()
print(f.__getstate__()) # See, *this* works
print('Pickle starts here')
print(pickle.dumps(f))
Run Code Online (Sandbox Code Playgroud)
然后给出:
Getting thing: foo (from: )
Getting thing: bar (from: foo)
Getting thing: woof (from: foo.bar)
Getting thing: last (from: foo.bar.woof)
b'Some data'
Pickle starts here
Getting thing: __spec__ (from: )
Getting thing: _initializing (from: __spec__)
Getting thing: foo (from: )
Getting thing: bar (from: foo)
Getting thing: woof (from: foo.bar)
Getting thing: last (from: foo.bar.woof)
Getting thing: __getstate__ (from: foo.bar.woof)
Traceback (most recent call last):
File "test.py", line 7, in <module>
print(pickle.dumps(f))
TypeError: 'Magic' object is not callable
Run Code Online (Sandbox Code Playgroud)
我不希望看到任何仰视__getstate__上module.foo.bar.woof,但即使我们强制将查找通过添加失败:
if name == '__getstate__': raise AttributeError()
Run Code Online (Sandbox Code Playgroud)
进入我们__getattr__仍然失败:
Traceback (most recent call last):
File "test.py", line 7, in <module>
print(pickle.dumps(f))
_pickle.PicklingError: Can't pickle <class 'module.Magic'>: it's not the same object as module.Magic
Run Code Online (Sandbox Code Playgroud)
是什么赋予了?我错过了什么__spec__吗?在对文档__spec__非常简单,只是强调进行恰当的设置,但似乎并没有真正解释了.
更重要的是,更大的问题是我应该如何通过伪模块的__getattr__实现pickle正确地以编程方式生成类型?
(显然,一旦我设法pickle.dumps生产了一些我期望用同样的东西pickle.loads调用undump的东西)
要pickle f,pickle需要picklef的类,module.foo.bar.woof.last。
这些文档没有声称支持对任意类进行酸洗。他们声称如下:
可以腌制以下类型:
- ...
- 在模块顶层定义的类
module.foo.bar.woof.last没有在模块的顶层定义,即使是像module. 在这种非官方支持的情况下,pickle 逻辑最终会尝试在此处module.foo.bar.woof进行pickle :
elif parent is not module:
self.save_reduce(getattr, (parent, lastname))
Run Code Online (Sandbox Code Playgroud)
或这里
else if (parent != module) {
PickleState *st = _Pickle_GetGlobalState();
PyObject *reduce_value = Py_BuildValue("(O(OO))",
st->getattr, parent, lastname);
status = save_reduce(self, reduce_value, NULL);
Run Code Online (Sandbox Code Playgroud)
module.foo.bar.woof由于多种原因无法腌制。它为所有不受支持的方法查找返回一个不可调用的Magic实例,例如__getstate__,这是您的第一个错误的来源。模块切换的事情阻止找到Magic类来腌制它,这就是第二个错误的来源。可能还有更多的不兼容之处。