泡菜和装饰类(PicklingError:不同的对象)

时间:2018-09-05 12:46:46

标签: python python-3.x decorator pickle python-decorators

下面的最小示例使用一个虚拟装饰器,该装饰器在构造装饰类的对象时仅打印一些消息。

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

有什么我可以解决的吗?

2 个答案:

答案 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)

输出->嗨