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

Tob*_*ann 2 python decorator pickle python-3.x 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)
Run Code Online (Sandbox Code Playgroud)

但是,使用它会pickle引发以下异常:

_pickle.PicklingError: Can't pickle <class '__main__.Foo'>: it's not the same object as __main__.Foo
Run Code Online (Sandbox Code Playgroud)

有什么我可以解决的吗?

Mar*_*ers 6

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

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

Pickle验证该类实际上可以首先导入。在__module____qualname__成对使用,以找到正确的模块,然后访问由指定的对象__qualname__在该模块上,并且如果__class__该模块上找到对象和对象不匹配,你看到出现错误。

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

有两种可能的解决方案:

  • 不要返回一个函数,不要返回原始的类,也许可以检测类对象来完成包装程序要做的工作。如果要对类构造函数的参数进行操作,请在装饰的类上添加或包装一个__new____init__方法。

    考虑到通常__new__在取消实例化之前,取消腌制会调用该类来创建一个新的空实例(除非已自定义腌制)。

  • 将课程存储在新位置。更改类__qualname____module__属性,并可能将其属性更改为指向可以通过pickle找到原始类的位置。取消拾取后,将再次创建正确的实例类型,就像原始Foo()调用一样。

另一种选择是为生产的类自定义酸洗。你可以给类新的__reduce_ex__新的__reduce__方法,这一点在包装功能或自定义功能降低,取而代之。这可能会变得很复杂,因为该类可能已经具有自定义的酸洗方法,并object.__reduce_ex__提供了默认值,并且返回值可能会因酸洗剂的版本而有所不同。

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

无论采用哪种方式,reducer的返回值都应避免引用该类,而应使用可以与其一起导入的名称引用新的构造函数。如果直接将修饰符与一起使用,则可能会出现问题new_name = decorator()(classobj)。泡菜本身也不会处理这种情况(因为classobj.__name__不会匹配newname)