Python - 如何使这个不可腌制的对象可腌制?

Posted

技术标签:

【中文标题】Python - 如何使这个不可腌制的对象可腌制?【英文标题】:Python - How can I make this un-pickleable object pickleable? 【发布时间】:2015-06-07 23:08:16 【问题描述】:

所以,我有一个对象,里面有很多不可腌制的东西(pygame 事件、orderedDicts、时钟等),我需要将它保存到磁盘。

问题是,如果我可以让这个东西存储一个有进度的字符串(我只需要一个整数),那么我可以将它传递给对象的 init,它会重建所有这些东西。不幸的是,我正在使用的框架 (Renpy) 会腌制对象并尝试加载它,尽管我可以将其保存为单个整数,但我无法更改。 p>

所以,我要问的是,我怎样才能覆盖方法,以便当 pickle 尝试保存对象时,它只保存进度值,并且每当它尝试加载对象时,它都会从进度值?

我已经看到一些关于 __repr__ 方法的讨论,但我不确定在我的情况下如何使用它。

【问题讨论】:

【参考方案1】:

您正在寻找的钩子是__reduce__。它应该返回一个(callable, args) 元组; callableargs 将被序列化,在反序列化时,对象将通过 callable(*args) 重新创建。如果您的类的构造函数采用 int,您可以将__reduce__ 实现为

class ComplicatedThing:
    def __reduce__(self):
        return (ComplicatedThing, (self.progress_int,))

您可以在元组中添加一些可选的额外内容,当您的对象图具有循环依赖关系时最有用,但您在这里不需要它们。

【讨论】:

所以,看起来系统正在使用 pickle.loads 而不是 load。这仍然有效吗?我收到一个错误 TypeError: __init__() 恰好需要 4 个参数(给定 1 个) 我的主对象不需要任何参数,但其中一些子对象需要。我可以让泡菜跳过它们吗?无论如何初始化主对象时,它们都会重新生成。 @MatthewFournier: loadsload 应该没关系。您的对象可以从单个 int 重构;你有这样做的功能吗?如果这样做,该函数应该是元组中的callable。如果你没有这样的功能,你可能需要写一个。 现在,***对象的 init 函数接受一个参数并重新构建它。我认为错误是指它的一些嵌套类(因为错误表明它需要四个参数)。有没有办法让pickler忽略这些对象?我根本不需要他们保存。我可以让他们减少到无或什么? @MatthewFournier:这很奇怪。我不明白你会如何得到这个错误。这不应该保存您不需要保存的对象。 (我的示例代码有一个错误,我忘记将参数包装在一个元组中,但这会产生不同的错误。) pickle 可以保存和重新加载有很多参数的对象吗?还是我需要重新设计所有内容以获取单个参数列表?【参考方案2】:

虽然使用__reduce__ 是一种有效的方法,但正如 Python 文档所述:

虽然功能强大,但直接在类中实现__reduce__() 很容易出错。因此,类设计人员应尽可能使用高级接口(即__getnewargs_ex__()__getstate__()__setstate__()

所以,我将解释如何使用更简单的高级接口__getstate____setstate__ 来使对象可拾取。

让我们看一个非常简单的类,它有一个 unpicklable 属性,假设它是一个文件句柄。

class Foo:
    def __init__(self, filename):
        self.filename = filename
        self.f = open(filename)

Foo 的实例不可选取:

obj = Foo('test.txt')
pickle.dumps(obj)
# TypeError: cannot pickle '_io.TextIOWrapper' object

我们可以通过分别实现__getstate____setstate__ 来使用pickle 使这个类可序列化和反序列化。

class Foo:
    ... # the class as it was
    def __getstate__(self):
       """Used for serializing instances"""
       
       # start with a copy so we don't accidentally modify the object state
       # or cause other conflicts
       state = self.__dict__.copy()

       # remove unpicklable entries
       del state['f']
       return state

    def __setstate__(self, state):
        """Used for deserializing"""
        # restore the state which was picklable
        self.__dict__.update(state)
        
        # restore unpicklable entries
        f = open(self.filename)
        self.f = f

现在可以腌制了:

obj = Foo('text.txt')
pickle.dumps(obj)
# b'\x80\x04\x951\x00\x00\x00\x00\x00\x00\x00\x8c\x08[...]'

因此,在您的示例中,您可能会执行以下操作:

class MyComplicatedObject:
    def __getstate__(self):
        state = self.__dict__.copy()
        del state['progress'] # remove the unpicklable progress attribute
        return state
    def __setstate__(self, state):
        self.__dict__.update(state)
        # restore the progress from the progress integer
        self.progress = make_progress(self.progress_int)

在 Python 3.8+ 中,您还可以实现 custom reductions for objects。

【讨论】:

以上是关于Python - 如何使这个不可腌制的对象可腌制?的主要内容,如果未能解决你的问题,请参考以下文章

对象可腌制(或可腌制)意味着啥?

如何在 Python 3 中腌制和取消腌制到可移植字符串

如何腌制一个scapy包?

如何使具有多个 init args 的自定义异常类可腌制

Python multiprocessing basic - 无法腌制本地对象并用尽输入

如何将数据库中的 Django 模型实例“腌制”到可用于加载示例数据的示例 python 代码中?