关于python 3.x:使用pickle保存对象状态(包含对象的对象)

Saving Object State with Pickle (objects containing objects)

我正在尝试解决如何将带有Pickle的对象序列化为保存文件。我的例子是一个名为World的对象,这个对象有一个list(名为objects),可能有数百个不同类类型的实例化对象。

问题是,Pickle不会让我序列化World.objects列表中的项目,因为它们没有被实例化为World的属性。

当我尝试序列化时:

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

我得到以下错误:

_pickle.PicklingError: Can't pickle :&attribute lookup LargeHealthPotion on world failed

所以,我的问题是:存储World.objects列表项的另一种方法是什么,以便它们是World的属性,而不是不保存的列表项?

更新我认为我的问题不是对象存储在哪里;而是类LargeHealthPotion和许多其他类是通过如下操作在World类中动态创建的:

1
2
3
4
5
6
7
8
9
10
11
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)

完成后,我将有一个新的对象,如。我动态地这样做是因为我不想显式地为我的世界中的每种不同类型的对象创建数百种不同类型的类。相反,我动态地创建类,同时遍历我想要创建的项名称(使用它的状态)。

但这会带来一个问题,因为在pickle时,它找不到对类的静态引用来解构或重建对象…所以失败了。

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


pickle不pickle类,而是依赖于对类的引用,如果类是动态生成的,则这些类不起作用。(此答案在文件中有适当的运用和加粗)

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

定义__reduce__以重建对象

我不确定如何向您演示此方法,我需要有关您的设置的更多信息来建议如何实现此方法,但我可以对其进行描述:

首先,您要创建一个函数或classmethod,它可以基于参数(可能采用类名、实例变量等)重新创建一个对象,然后在对象基类上定义__reduce__,该类将返回该函数以及取消拾取时传递给它所需的参数。

将动态类放在全局范围内

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

不使用动态类

在我看来,这绝对是一种方法,而不是使用type构造函数,只需定义您自己的类,名为ObjectType之类的:

  • 不是type的子类,因此这些实例是可pickle的。
  • 当一个实例被调用时,它会构造一个新的游戏对象,该对象具有对该对象类型的引用。

因此,假设您有一个名为GameObject的类,它采用cls=的方法,那么您可以设置ObjectType类,如下所示:

1
2
3
4
5
6
7
8
9
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__魔术方法,所以它使用与类cls(params)相同的符号来创建实例,cls=self会向(抽象的)GameObject构造函数指示GameObject的类(类型)是基于ObjectType实例self的。它不必是关键字参数,但我不知道如何在不了解更多程序的情况下生成一致的示例代码。