使用 Pickle 保存对象状态(对象包含对象)

Saving Object State with Pickle (objects containing objects)

我正在尝试弄清楚如何将具有 Pickle 的对象序列化到保存文件中。我的示例是一个名为 World 的对象,该对象有一个 list(名为 objects),可能包含数百个不同 class 类型的实例化对象。

问题是 Pickle 不允许我序列化 World.objects 列表中的项目,因为它们没有实例化为 World.

的属性

当我尝试序列化时:

with open('gsave.pkl', 'wb') as output:
    pickle.dump(world.objects, output, pickle.DEFAULT_PROTOCOL)

我收到以下错误:

_pickle.PicklingError: Can't pickle <class 'world.LargeHealthPotion'>:
attribute lookup LargeHealthPotion on world failed

所以,我的问题是:有什么替代方法可以存储 world.objects 列表项,使它们成为 world 的属性,而不是不保存的列表项?

更新 我认为我的问题不在于对象的存储位置;而是 class LargeHealthPotion(以及许多其他)是在 World class 中通过以下操作动态创建的:

def __constructor__(self, n, cl, d, c, h, l):
    # initialize super of class type
    super(self.__class__, self).__init__(name=n, classtype=cl, description=d, cost=c,
                                         hp=h, level=l)
    # create the object class dynamically, utilizing __constructor__ for __init__ method
    item = type(item_name,
                (eval("{}.{}".format(name,row[1].value)),),
                {'__init__':__constructor__})
    # add new object to the global _objects object to be used throughout the world
    self._objects[item_name] = item(obj_name, obj_classtype, obj_description, obj_cost,
                                    obj_hp, obj_level)

完成后,我将拥有一个像 <world.LargeHealthPotion object at 0x103690ac8> 这样的新对象。我这样做是动态的,因为我不想明确地为我的世界中的每种不同类型的对象创建数百种不同类型的 classes。相反,我在迭代要创建的项目名称(及其统计信息)时动态创建 class。

但这引入了一个问题,因为在酸洗时,它无法找到对 class 的静态引用以解构或重建对象...因此失败。

我还能做什么? (除了为每种类型的对象创建文字 class 引用外,我将实例化到我的世界中。)

Pickle 不 pickle classes,它依赖于对 classes 的引用,如果 class 是动态生成的,它就不起作用。 (this answer has appropriate exert and bolding from documentation)

因此 pickle 假定如果您的对象来自 class 称为 world.LargeHealthPotion 那么它会检查该名称是否实际解析为 class 它将能够在 unpickling 时使用,如果不是,那么您将无法重新初始化该对象,因为它不知道如何引用 class。有几种方法可以解决这个问题:

定义__reduce__重构对象

我不确定如何向您演示此方法,我需要更多关于您的设置的信息来建议如何实施此方法,但我可以描述它:

首先你会创建一个函数或 classmethod 可以根据参数重新创建一个对象(可能采用 class 名称、实例变量等)然后定义 __reduce__对象基础 class 将 return 与解封时需要传递给它的参数一起运行。

将动态 classes 放入全局范围

这是快速而肮脏的解决方案。假设 class 名称与 world 模块中定义的其他内容不冲突,理论上您可以通过执行 globals()[item_name] = item_type 将 classes 插入全局范围,但我没有推荐这个作为长期解决方案,因为这是非常糟糕的做法。

不要使用动态 classes

这绝对是我认为的方式,而不是使用 type 构造函数,只需定义您自己的 class 命名为 ObjectType 这样的东西:

  • 不是 type 的子class,所以实例可以 pickle-able。
  • 当实例被调用时,它会构造一个新的游戏对象,该对象具有对对象类型的引用。

所以假设你有一个名为 GameObject 的 class 需要 cls=<ObjectType object> 你可以设置 ObjectType class 像这样:

class ObjectType:
    def __init__(self, name, description):
        self.item_name = name
        self.base_item_description = description
        #other qualities common to all objects of this type

    def __call__(self, cost, level, hp):
        #other qualities that are specific to each item
        return GameObject(cls=self, cost=cost, level=level, hp=hp)

我在这里使用 __call__ magic method 所以它使用与 classes cls(params) 相同的符号来创建实例,cls=self 将指示(抽象) GameObject GameObject 的 class(类型)基于 ObjectType 实例 self 的构造函数。它不一定是关键字参数,但我不确定在不了解您的程序的情况下如何制作连贯的示例代码。