本文介绍了泡菜和装饰类(PicklingError:不同的对象)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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__属性.

酸洗实例仅存储实例数据,该类的__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函数的新__reduce__ 方法, 反而.这可能会变得很复杂,因为该类可能已经具有自定义的酸洗功能,并且object.__reduce_ex__提供了默认值,返回值可能会因泡菜版本而异.

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

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

The following minimal example uses a dummy decorator, that justs prints some message when an object of the decorated class is constructed.

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)

Using it however makes pickle raise the following exception:

_pickle.PicklingError: Can't pickle <class '__main__.Foo'>: it's not the same object as __main__.Foo

Is there anything I can do to fix this?

解决方案

Pickle requires that the __class__ attribute of instances can be loaded via importing.

Pickling instances only stores the instance data, and the __qualname__ and __module__ attributes of the class are used to later on re-create the instance by importing the class again and creating a new instance for the class.

Pickle validates that the class can actually be imported first. The __module__ and __qualname__ pair are used to find the correct module and then access the object named by __qualname__ on that module, and if the __class__ object and the object found on the module don't match, the error you see is raised.

Here, foo.__class__ points to a class object with __qualname__ set to 'Foo' and __module__ set to '__main__', but sys.modules['__main__'].Foo doesn't point to a class, it points to a function instead, the wrapper nested function your decorator returned.

There are two possible solutions:

  • Don't return a function, return the original class, and perhaps instrument the class object to do the work the wrapper does. If you are acting on the arguments for the class constructor, add or wrap a __new__ or __init__ method on the decorated class.

    Take into account that unpickling usually calls __new__ on the class to create a new empty instance, before restoring the instance state (unless pickling has been customised).

  • Store the class under a new location. Alter the __qualname__ and perhaps the __module__ attributes of the class to point to a location where the original class can be found by pickle. On unpickling the right type of instance will be created again, just like the original Foo() call would have.

Another option is to customise pickling for the produced class. You can give the class new __reduce_ex__ and new __reduce__ methods that point to the wrapper function or a custom reduce function, instead. This can get complex, as the class may already have customised pickling, and object.__reduce_ex__ provides a default, and the return value can differ by pickle version.

If you don't want to alter the class, you can also use the copyreg.pickle() function to register a custom __reduce__ handler for the class.

Either way, the return value of the reducer should still avoid referencing the class and should reference the new constructor instead, by the name that it can be imported with. This can be problematic if you use the decorator directly with new_name = decorator()(classobj). Pickle itself would not deal with such situations either (as classobj.__name__ would not match newname).

这篇关于泡菜和装饰类(PicklingError:不同的对象)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

10-13 13:16