【问题标题】:Saving Object State with Pickle (objects containing objects)使用 Pickle 保存对象状态(包含对象的对象)
【发布时间】:2017-05-14 00:52:14
【问题描述】:

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

问题是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 的属性,而不是未保存的列表项?

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

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> 这样的新对象。我动态地这样做是因为我不想显式地为我的世界中每种不同类型的对象创建数百个不同类型的类。相反,我在迭代我想要创建的项目名称(及其统计信息)时动态创建类。

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

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

【问题讨论】:

  • 而不是创建数千个类,您可以拥有一个信息字典(名称、类类型、描述、成本、hp、级别),然后每个对象都有一个属性,将其指向字典中的正确条目?
  • 您甚至可以使用property 进行查找,这样您仍然可以像访问类属性一样访问信息。 PS:你可以看看stackoverflow.com/questions/4235078/…
  • @TadhgMcDonald-Jensen,关于字典的想法,我并不完全关注你。我意识到我可以将所有项目存储在字典中,但我仍然必须创建对象来引用字典元素的每个数据点。这些对象会导致与 pickle 相同的问题,因为我必须动态创建对象。

标签: python-3.x oop object pickle


【解决方案1】:

Pickle 不会腌制类,而是依赖于对类的引用,如果类是动态生成的,则这些引用不起作用。 (this answerdocumentation 有适当的加粗和加粗)

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

定义__reduce__重构对象

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

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

将动态类放在全局范围内

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

不要使用动态类

在我看来,这绝对是要走的路,而不是使用 type 构造函数,只需定义自己的类,命名为 ObjectType 之类的:

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

因此,假设您有一个名为 GameObject 的类采用 cls=<ObjectType object>,您可以像这样设置 ObjectType 类:

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,因此它使用与cls(params) 类相同的符号来创建实例,cls=self 将向(抽象的)GameObject 构造函数表明@987654343 的类(类型) @ 基于ObjectType 实例self。它不一定是关键字参数,但我不确定如何在不了解您的程序的情况下制作连贯的示例代码。

【讨论】:

  • 这是一个很好的答案。我不确定我是否完全理解了 call 参考部分,但我会暂时搁置一下,看看它是否开始有意义。感谢您的详细回复;我同意,不使用动态类是最好的方法。
  • 您能帮我了解更多__call__ 参考吗?我不确定我是否了解__call__ 是如何执行的,此外,应该如何构造(在您的示例中)game_object 以使用cls=self 参数。
  • __call__ 魔术方法在调用对象时使用,因此您可以像调用动态类来创建游戏对象一样调用“ObjectType”对象,我想通了会减少你需要编写实现的额外代码的数量,但我想我应该在答案中给出更多的解释......
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-06-17
  • 1970-01-01
  • 2014-05-30
  • 1970-01-01
相关资源
最近更新 更多