泡菜和装饰类(PicklingError:不是同一个对象)

问题描述 投票:0回答:1

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

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

有什么办法可以解决这个问题吗?

python python-3.x decorator pickle python-decorators
1个回答
3
投票

Pickle要求可以通过导入加载实例的__class__属性。

Pickling实例仅存储实例数据,类的__qualname____module__属性用于稍后通过再次导入类并为类创建新实例来重新创建实例。

Pickle验证该类实际上可以先导入。 __module____qualname__对用于查找正确的模块,然后在该模块上访问由__qualname__命名的对象,如果__class__对象和模块上找到的对象不匹配,则会看到您看到的错误。

在这里,foo.__class__指向一个类对象,__qualname__设置为'Foo'__module__设置为'__main__',但sys.modules['__main__'].Foo不指向类,它指向一个函数,而你的装饰器返回的wrapper嵌套函数。

有两种可能的解决方案:

  • 不返回函数,返回原始类,也许可以检测类对象来完成包装器所做的工作。如果您正在处理类构造函数的参数,请在装饰类上添加或包装__new____init__方法。 考虑到在恢复实例状态之前,unpickling通常会在类上调用__new__来创建一个新的空实例(除非pickle已经是customised)。
  • 将课程存放在新位置。改变__qualname__和类的__module__属性,指向可以通过pickle找到原始类的位置。在unpickling上,将再次创建正确类型的实例,就像原始的Foo()调用一样。

另一种选择是为生产的类定制酸洗。您可以给类指向包装函数或自定义reduce函数的类new __reduce_ex__new __reduce__方法。这可能变得复杂,因为类可能已经有自定义酸洗,而object.__reduce_ex__提供了默认值,并且返回值可能因pickle版本而不同。

如果您不想更改类,还可以使用copyreg.pickle() function为类注册自定义__reduce__处理程序。

无论哪种方式,reducer的返回值仍然应该避免引用该类,并且应该引用新的构造函数,而不是引用它可以导入的名称。如果直接使用装饰器与new_name = decorator()(classobj),这可能会有问题。 Pickle本身也不会处理这种情况(因为classobj.__name__不会匹配newname)

© www.soinside.com 2019 - 2024. All rights reserved.