假设我们有一个这样的类定义:
class Foo:
class Bar:
def __init__(self, _):
pass # Imagine some implementation
def __init__(self, a):
self._a = Foo.Bar(a)
然后我们实例化它并使用 pickle 将对象转储到文件中。
import pickle
obj = Foo('whatever')
pickle.dump(obj, open('./tmp.pkl', 'wb'))
现在假设我们对类的命名或结构进行更改(在本例中,“Bar”被重命名为“Baz”)。
class Foo:
class Baz:
def __init__(self, _):
pass # Imagine some implementation
def __init__(self, a):
self._a = Foo.Baz(a)
如果我们最终尝试在这种情况下加载转储的对象,如下所示:
obj = pickle.load(open('./tmp.pkl', 'rb'))
我们会给我们一个...
AttributeError: Can't get attribute 'Foo.Bar' on <module '__main__' from 'myscript.py'>
问题:有没有一种方法可以独立于“Foo”的当前实现来加载pickle对象? 由于我们在 Python 中有
__dict__
属性,我想可能有一个选项可以将 pickle 对象作为字典来获取?
除了非常有问题的嵌套类设计之外,通过 Pickle 进行的 Python 序列化是相当可定制的。
例如,可以编写一个
__setstate__
方法,在 unpickling 时将调用该方法来为重构的类赋值。
在这种情况下它不会有帮助,因为Python会首先尝试取消嵌套属性 -
self._a
“Foo.Bar”实例,然后才尝试创建一个新的Foo
实例,设置._a
价值。
您可以在 pickling 时使用
__getstate__
方法自定义 pickle 文件,并将新的 Baz
类存储在序列化文件中 - 并且构建一个包含这两个类并执行此转换的中间版本是可行的.
或者,在这种情况下更容易的是,只需在 Unpickle 时使用某种机制创建类来重新映射嵌套类名称 - 我认为它需要一个带有
__getattr__
的元类 - 它将在 unpickle 时调用未找到嵌套在“Foo”中的类的名称,并且可以只返回新创建的类:
class M(type):
def __getattr__(cls, attr):
if attr == "Bar":
return getattr(cls, "Baz")
raise AttributeError()
class Foo(metaclass=M):
class Baz:
def __init__(self, _):
pass # Imagine some implementation
def __init__(self, a):
self._a = Foo.Baz(a)
上面的“Foo”实例可以取消序列化的类似类,该类的内部类被命名为“Bar”。当然,如果有多个这样的实例,您可以只使用元类内部的映射'
__getattr__
而不是硬编码的“if”