6

我正在使用 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
4

5 回答 5

5

简而言之:不要这样做。

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

于 2012-12-03T20:33:05.893 回答
4

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

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

  1. 当您腌制一个对象时,其成员变量引用的所有对象都将自动腌制/取消腌制,这很好
  2. 如果pickle不能序列化一个对象,你应该告诉pickle如何保存和恢复该对象,方法是为任何不pickle的对象编写自定义getstatesetstate方法,所以嵌套在主会话对象中的一两个类将具有自定义get/setstate函数来执行诸如重新打开设备之类的操作,例如文件句柄,这在会话之间显然会有所不同
  3. 如果您需要进行二进制序列化,则不需要将对象转换为字符串,只需在该对象的get/setstate方法中使用二进制序列化协议(即使用协议 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()
于 2012-12-03T21:52:03.093 回答
2

如果您知道所有不可腌制的对象类型,那么这个问题的答案中的代码可能会有所帮助“递归 dir() 一个 python 对象以查找某种类型或具有某个值的值”——我写它是为了回应类似的情况,我知道所有不可腌制的对象类型,但我不知道它们在数据结构中的位置。您可以使用此代码找到它们,将它们替换为其他内容,然后在 unpickling 时使用类似的代码将它们放回原处。

于 2012-12-03T21:01:50.263 回答
2

从您的评论中,听起来您尝试做的最困难的部分是为用户提供实时解释器,并保存.

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

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

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

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

于 2012-12-03T20:52:01.960 回答
2

为此,我使用dill,它可以在 python 中序列化几乎任何东西。Dill 还提供了一些很好的工具,可以帮助您了解在代码失败时导致酸洗失败的原因。此外,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')
于 2013-10-14T13:13:55.340 回答