Pickle and decorated classes (PicklingError: not the same object)
Pickle and decorated classes (PicklingError: not the same object)
下面的最小示例使用了一个虚拟装饰器,它只是在构造装饰器 class 的对象时打印一些消息。
import pickle
def decorate(message):
def call_decorator(func):
def wrapper(*args, **kwargs):
print(message)
return func(*args, **kwargs)
return wrapper
return call_decorator
@decorate('hi')
class Foo:
pass
foo = Foo()
dump = pickle.dumps(foo) # Fails already here.
foo = pickle.loads(dump)
然而,使用它会使 pickle
引发以下异常:
_pickle.PicklingError: Can't pickle <class '__main__.Foo'>: it's not the same object as __main__.Foo
我能做些什么来解决这个问题吗?
Pickle 要求可以通过导入加载实例的 __class__
属性。
Pickling 实例仅存储实例数据,class 的 __qualname__
和 __module__
属性用于稍后通过导入 [=71] 重新创建实例=] 并为 class.
创建一个新实例
Pickle 验证 class 实际上可以首先导入。 __module__
和 __qualname__
对用于找到正确的模块,然后访问该模块上由 __qualname__
命名的对象,如果 __class__
对象和在上找到的对象模块不匹配,出现您看到的错误。
这里,foo.__class__
指向一个class对象,__qualname__
设置为'Foo'
,__module__
设置为'__main__'
,但是sys.modules['__main__'].Foo
不指向 class,它指向一个函数,wrapper
嵌套函数你的装饰器 returned.
有两种可能的解决方案:
不要 return 一个函数,return 原来的 class,也许可以检测 class 对象来完成包装器的工作做。如果您正在对 class 构造函数的参数进行操作,请在修饰的 class.
上添加或包装 __new__
或 __init__
方法
考虑到 unpickling 通常在 class 上调用 __new__
来创建一个新的空实例,然后再恢复实例状态(除非 pickling 已经 customised)。
将 class 存储在新位置。更改 __qualname__
以及 class 的 __module__
属性,使其指向可以通过 pickle 找到原始 class 的位置。在 unpickling 上,将再次创建正确类型的实例,就像最初的 Foo()
调用一样。
另一种选择是为生产的 class 定制酸洗。您可以提供指向包装函数或自定义 reduce 函数的 class new __reduce_ex__
and new __reduce__
方法。这可能会变得复杂,因为 class 可能已经自定义了酸洗,而 object.__reduce_ex__
提供了默认值, 和 return 值可能因酸洗而不同版本。
如果您不想更改 class,您还可以使用 copyreg.pickle()
function 为 class 注册自定义 __reduce__
处理程序。
无论哪种方式,reducer 的 return 值仍应避免引用 class,而应引用新的构造函数,使用可以导入的名称。如果您直接将装饰器与 new_name = decorator()(classobj)
一起使用,这可能会出现问题。 Pickle 本身也不会处理这种情况(因为 classobj.__name__
不会匹配 newname)
.
使用 dill 而不是 pickle 不会引发错误。
import dill
def decorate(message):
def call_decorator(func):
def wrapper(*args, **kwargs):
print(message)
return func(*args, **kwargs)
return wrapper
return call_decorator
@decorate('hi')
class Foo:
pass
foo = Foo()
dump = dill.dumps(foo) # Fails already here.
foo = dill.loads(dump)
输出 -> hi
下面的最小示例使用了一个虚拟装饰器,它只是在构造装饰器 class 的对象时打印一些消息。
import pickle
def decorate(message):
def call_decorator(func):
def wrapper(*args, **kwargs):
print(message)
return func(*args, **kwargs)
return wrapper
return call_decorator
@decorate('hi')
class Foo:
pass
foo = Foo()
dump = pickle.dumps(foo) # Fails already here.
foo = pickle.loads(dump)
然而,使用它会使 pickle
引发以下异常:
_pickle.PicklingError: Can't pickle <class '__main__.Foo'>: it's not the same object as __main__.Foo
我能做些什么来解决这个问题吗?
Pickle 要求可以通过导入加载实例的 __class__
属性。
Pickling 实例仅存储实例数据,class 的 __qualname__
和 __module__
属性用于稍后通过导入 [=71] 重新创建实例=] 并为 class.
Pickle 验证 class 实际上可以首先导入。 __module__
和 __qualname__
对用于找到正确的模块,然后访问该模块上由 __qualname__
命名的对象,如果 __class__
对象和在上找到的对象模块不匹配,出现您看到的错误。
这里,foo.__class__
指向一个class对象,__qualname__
设置为'Foo'
,__module__
设置为'__main__'
,但是sys.modules['__main__'].Foo
不指向 class,它指向一个函数,wrapper
嵌套函数你的装饰器 returned.
有两种可能的解决方案:
不要 return 一个函数,return 原来的 class,也许可以检测 class 对象来完成包装器的工作做。如果您正在对 class 构造函数的参数进行操作,请在修饰的 class.
上添加或包装__new__
或__init__
方法考虑到 unpickling 通常在 class 上调用
__new__
来创建一个新的空实例,然后再恢复实例状态(除非 pickling 已经 customised)。将 class 存储在新位置。更改
__qualname__
以及 class 的__module__
属性,使其指向可以通过 pickle 找到原始 class 的位置。在 unpickling 上,将再次创建正确类型的实例,就像最初的Foo()
调用一样。
另一种选择是为生产的 class 定制酸洗。您可以提供指向包装函数或自定义 reduce 函数的 class new __reduce_ex__
and new __reduce__
方法。这可能会变得复杂,因为 class 可能已经自定义了酸洗,而 object.__reduce_ex__
提供了默认值, 和 return 值可能因酸洗而不同版本。
如果您不想更改 class,您还可以使用 copyreg.pickle()
function 为 class 注册自定义 __reduce__
处理程序。
无论哪种方式,reducer 的 return 值仍应避免引用 class,而应引用新的构造函数,使用可以导入的名称。如果您直接将装饰器与 new_name = decorator()(classobj)
一起使用,这可能会出现问题。 Pickle 本身也不会处理这种情况(因为 classobj.__name__
不会匹配 newname)
.
使用 dill 而不是 pickle 不会引发错误。
import dill
def decorate(message):
def call_decorator(func):
def wrapper(*args, **kwargs):
print(message)
return func(*args, **kwargs)
return wrapper
return call_decorator
@decorate('hi')
class Foo:
pass
foo = Foo()
dump = dill.dumps(foo) # Fails already here.
foo = dill.loads(dump)
输出 -> hi