【问题标题】:Pickling unpicklable objects酸洗不可腌制的物体
【发布时间】:2012-11-21 09:17:31
【问题描述】:

我正在使用 pygame 制作一个绘图程序,我想在其中为用户提供一个选项,即保存程序的确切状态,然后在以后重新加载它。 在这一点上,我保存了我的 globals dict 的副本,然后遍历,腌制每个对象。 pygame 中有一些对象不能被腌制,但可以转换成字符串并以这种方式腌制。我的代码设置为执行此操作,但其中一些不可提取的对象是通过引用到达的。换句话说,它们不在全局字典中,但它们被全局字典中的对象引用。我想在这个递归中腌制它们,但我不知道如何告诉 pickle 返回它遇到问题的对象,更改它,然后尝试再次腌制它。我的代码真的很杂乱无章,如果有其他更好的方法来做我想做的事情,请告诉我。


surfaceStringHeader = 'PYGAME.SURFACE_CONVERTED:'
imageToStringFormat = 'RGBA'
def save_project(filename=None):
    assert filename != None, "Please specify path of project file"
    pickler = pickle.Pickler(file(filename,'w'))
    for key, value in globals().copy().iteritems():
        #There's a bit of a kludge statement here since I don't know how to 
        #access module type object directly
        if type(value) not in [type(sys),type(None)]   and \
        key not in ['__name__','value','key']          and \
        (key,value) not in pygame.__dict__.iteritems() and \
        (key,value) not in sys.__dict__.iteritems()    and \
        (key,value) not in pickle.__dict__.iteritems(): 
        #Perhaps I should add something to the above to reduce redundancy of
        #saving the program defaults?
            #Refromat unusable objects:
            if type(value)==pygame.Surface:
                valueString = pygame.image.tostring(value,imageToStringFormat)
                widthString = str(value.get_size()[0]).zfill(5)
                heightString = str(value.get_size()[1]).zfill(5)
                formattedValue = surfaceStringHeader+widthString+heightString+valueString
            else:
                formattedValue = value

            try:
                pickler.dump((key,formattedValue))
            except Exception as e:
                print key+':' + str(e)

def open_project(filename=None):
    assert filename != None, "Please specify path to project file"
    unpickler = pickle.Unpickler(file(filename,'r'))
    haventReachedEOF = False
    while haventReachedEOF:
        try:
            key,value = unpickler.load()
            #Rework the unpicklable objects stored 
            if type(value) == str and value[0:25]==surfaceStringHeader:
                value = pygame.image.frombuffer(value[36:],(int(value[26:31]),int(value[31:36])),imageToStringFormat)
            sys.modules['__main__'].__setattr__(key,value)
        except EOFError:
            haventReachedEOF = True

【问题讨论】:

  • 这看起来很麻烦。明确说明您要保存的值,这样您就可以省去很多麻烦。
  • 第二。我可以从经验中说,精确定义程序的状态是一些前期工作,但会鼓励更好的设计并迫使你仔细考虑你的状态(这在调试时会派上用场)。
  • 你的问题的难点在于你的程序状态包括为用户运行一个实时解释器,它可以用来修改绘图的状态。您在原始问题的任何地方都没有提到这一点。

标签: python pygame pickle save savestate


【解决方案1】:

简而言之:不要这样做。

腌制应用程序中的所有内容很麻烦,并且可能会导致问题。从程序中获取您需要的数据并手动将其存储为适当的数据格式,然后通过从该数据中创建您需要的内容来加载它。

【讨论】:

  • 该程序与实时解释器一起运行,允许用户重写或添加函数和数据结构。我想在他们的项目加载时保留和重新定义这些,所以我不能将酸洗限制为预定义的变量。有没有办法保存python程序的状态?
  • @hedgehogrider:没有办法在所有情况下都保存 Python 程序的完整状态。 ipythonscipy 对此想法的实现有限,超出了 pickle(就此而言,multiprocessingpickle 扩展到股票之外),因此您可能希望向他们寻求想法。但是您是否需要用户能够,例如,猴子修补您的框架的一部分并使其可恢复?
  • 您所建议的并不是一项简单的任务。这是否可能超出了我的知识范围,但您可能想问问自己,确实需要有这么大的范围。
【解决方案2】:

您想保存整个程序的状态,以便以后可以重新加载。这是 Pickle 的一个完美用例,我完全看不出这个用例有什么问题。但是,您腌制 globals() 命名空间并过滤掉 sys、pygame 和 pickle 的方法很不稳定。通常的模式是拥有一个您腌制的会话对象。

另外我认为如何腌制可能会有些混乱:

  1. 当您腌制一个对象时,它所引用的所有对象 成员变量将被自动腌制/取消腌制,即 不错
  2. 如果pickle不能序列化一个对象,你应该告诉pickle 如何通过为任何不腌制的对象编写自定义 getstate and setstate 方法来保存和恢复该对象,所以 你的一两个类嵌套在你的主人里面 session 对象将具有自定义的 get/setstate 功能 诸如重新打开文件句柄之类的设备之类的东西显然会 会话之间的不同
  3. 如果需要进行二进制序列化 您不需要将对象转换为字符串,只需使用二进制文件 该对象的 get/setstate 方法中的序列化协议, (ie use Protocol 1)

最后你的代码应该看起来更像这样:

session = None
import pickle
def startsession():
    globals session
    session = pickle.Unpickler(sessionfilehandle('r')).load()
    if session is None: session = Session() 

def savesession(filename=None):
    globals session
    pickle.Pickler.dump(session,sessionfilehandle('w'))

class Session(object):
    def __init__(self):
        self.someobject=NewObject1()
        #.... plus whole object tree representing the whole game
        self.somedevicehandlethatcannotbepickled=GetDeviceHandle1()  #for example
    def __getstate__(self):
        odict = self.__dict__.copy()
        del odict['somedevicehandlethatcannotbepickled'] #don't pickle this
        return odict
    def __setstate__(self, dict):
        self.__dict__.update(dict)
        self.somedevicehandlethatcannotbepickled=GetDeviceHandle1()

【讨论】:

  • 顺便说一句,我没有看到保存现场口译员状态的问题。它只是另一个包含成员字典的对象,该字典是用户的命名空间。字典马上腌制,没什么特别的。您可能已经使用 exec 关键字执行此自定义用户代码,其格式指定要针对哪个命名空间运行。
  • 非常感谢里亚兹的回答!但是,问题:如何将另一个方法注入到我没有创建的现有类中?我应该将它子类化,然后将原始设置为子类还是有更优雅的方法来做到这一点?
  • 然后腌制。将另一种方法注入现有类,只要它们像普通的 python 类一样可扩展,那么应该没有问题。对于像 dict 这样不可扩展子类的类: class MyDict(dict): pass 就可以了。虽然这里可以说更多。创建一个单独的问题可能会更好。
  • 感谢您的跟进!我会单独研究的!
  • 理论上这是可行的,但在实践中并不实用……或者至少需要大量艰苦的工作(即可能需要几个月到几年的时间)。您是否尝试过按照您的建议进行操作(特别是#2 和您的代码)? OP 正在解释器中处理游戏……所以 任何 对象都可能在会话中。
【解决方案3】:

从您的 cmets 看来,您尝试做的最困难的部分是为用户提供实时解释器,并保存 that 的状态。

那么,如何将实时解释器作为子进程运行呢?您想要公开给脚本的任何来自对象模型的信息,您都明确地这样做(无论是通过multiprocessing 共享内存,还是某种消息传递 API)。

然后,您不需要保存自己的解释器的完整状态,这要么非常困难,要么不可能;您以正常方式保存数据模型,然后您可以从外部而不是内部冻结子解释器。

这显然比您尝试做的要复杂得多,但我认为任何简单的事情实际上都行不通。例如,如果用户对你的代码有一个实时解释器,他们可以对任何东西进行猴子补丁——甚至是酸洗代码——然后会发生什么?您需要对可以保存和恢复的确切内容定义一些限制——如果这些限制足够广泛,我认为您必须从外部进行。

同时,正如评论中提到的,scipy(或 Enthought 附带的一些相关项目)和ipython 都具有针对有限用例的保存和恢复功能,这至少为您提供了一些学习代码,但它们的用例可能与您的不同。

【讨论】:

    【解决方案4】:

    如果你知道所有不可腌制的对象类型,那么这个问题的答案中的代码可能会有所帮助“Recursively dir() a python object to find values of a certain type or with a certain value”——我写它是为了回应类似的情况,我知道所有不可腌制的对象类型,但我不能不知道它们在数据结构中的位置。您可以使用此代码找到它们,将它们替换为其他内容,然后在 unpickling 时使用类似的代码将它们放回原处。

    【讨论】:

      【解决方案5】:

      为此,我使用dill,它可以序列化python 中的几乎任何东西。 Dill 还有some good tools 可以帮助您了解在代码失败时导致酸洗失败的原因。此外,objgraph 也是对测试套件的一个非常方便的补充。

      >>> import dill
      >>> # blah blah blah... your session code here
      >>> dill.dump_session('pygame.pkl')
      >>>
      >>> # and if you get a pickling error, use dill's tools to discover a workaround
      >>> dill.detect.badobjects(your_bad_object, depth=1)
      >>>
      >>> # visualize the references in your bad objects
      >>> objgraph.show_refs(your_bad_object, filename='pygame_bad_object.png')
      

      【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-09-15
      • 1970-01-01
      • 2011-05-04
      • 1970-01-01
      • 1970-01-01
      • 2014-05-29
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多