下面的最小示例使用一个虚拟装饰器,该装饰器在构造装饰类的对象时仅打印一些消息。
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
有什么我可以解决的吗?
答案 0 :(得分:3)
Pickle要求可以通过导入来加载实例的__class__
属性。
酸洗实例仅存储实例数据,该类的__qualname__
和__module__
属性用于稍后通过再次导入该类并为该实例创建新实例来重新创建实例。课。
Pickle验证该类实际上可以首先导入。 __module__
和__qualname__
对用于查找正确的模块,然后访问该模块上由__qualname__
命名的对象,以及是否找到__class__
对象和该对象。模块不匹配,则会出现您看到的错误。
在这里,foo.__class__
指向一个类对象,其中__qualname__
设置为'Foo'
,而__module__
设置为'__main__'
,但是sys.modules['__main__'].Foo
并不t指向类,而是指向函数,装饰器返回的wrapper
嵌套函数。
有两种可能的解决方案:
不返回函数,返回原始类,也许不检测类对象以完成包装程序要做的工作。如果您要作用于类构造函数的参数,请在装饰的类上添加或包装__new__
或__init__
方法。
请注意,在恢复实例状态之前(除非进行了customised的酸洗),通常,取消酸洗通常会在类上调用__new__
来创建一个新的空实例。
将类存储在新位置。更改类的__qualname__
以及可能的__module__
属性,使其指向可通过pickle找到原始类的位置。取消选择后,将再次创建正确的实例类型,就像原始的Foo()
调用一样。
另一个选择是自定义生产类的酸洗。您可以给类new __reduce_ex__
和new __reduce__
指向包装函数或自定义的reduce函数的方法。这可能会变得很复杂,因为该类可能已经具有自定义的酸洗功能,并且object.__reduce_ex__
提供了默认值,和返回值可能会因泡菜版本而异。
如果您不想更改类,还可以使用copyreg.pickle()
function为该类注册自定义__reduce__
处理程序。
无论哪种方式,reduce的返回值仍应避免引用该类,而应使用可以导入的名称引用新的构造函数。如果直接将装饰器与new_name = decorator()(classobj)
一起使用,则可能会出现问题。泡菜本身也不会处理这种情况(因为classobj.__name__
与newname)
不匹配。
答案 1 :(得分:0)
使用莳萝,而不是泡菜,不会出现任何错误。
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)
输出->嗨