使用pickle导出gtk.ListStore的子类

4 投票
2 回答
812 浏览
提问于 2025-04-16 17:27

我正在尝试使用pickle来保存一个自定义类。这个类是从gtk.ListStore派生出来的,因为这样可以更方便地存储特定的数据,然后通过gtk来显示这些数据。这个问题可以通过下面的代码复现。

import gtk
import pickle
import os

class foo(gtk.ListStore):
    pass

if __name__=='__main__':
    x = foo(str)
    with open(os.path.expandvars('%userprofile%\\temp.txt'),'w') as f:
        pickle.dump(x,f)

我尝试的解决方案是给我的类添加一个__getstate__函数。根据我对文档的理解,这个函数应该优先被调用,这样pickle就不会再尝试序列化ListStore,因为它无法做到这一点。然而,当我尝试保存我的对象时,pickle.dump依然报了同样的错误。这个错误可以通过以下代码复现。

import gtk
import pickle
import os

class foo(gtk.ListStore):
    def __getstate__(self):
        return 'bar'

if __name__=='__main__':
    x = foo(str)
    with open(os.path.expandvars('%userprofile%\\temp.txt'),'w') as f:
        pickle.dump(x,f)

在每种情况下,pickle.dump都会抛出一个TypeError,提示“无法序列化ListStore对象”。通过打印语句,我确认在使用pickle.dump时__getstate__函数确实被调用了。但文档中没有给出下一步该怎么做的提示,所以我有点困惑。有没有什么建议?

2 个回答

0

当你创建一个对象的子类时,object.__reduce__ 会负责调用 __getstate__。看起来因为这是 gtk.ListStore 的一个子类,所以 __reduce__ 的默认实现会先尝试把 gtk.ListStore 对象的数据进行序列化(也就是“打包”),然后再调用你的 __getstate__。但是因为 gtk.ListStore 不能被序列化,所以它拒绝对你的类进行序列化。如果你尝试实现 __reduce____reduce_ex__,而不是 __getstate__,这个问题应该就能解决了。

>>> class Foo(gtk.ListStore):
...     def __init__(self, *args):
...             super(Foo, self).__init__(*args)
...             self._args = args
...     def __reduce_ex__(self, proto=None):
...             return type(self), self._args, self.__getstate__()
...     def __getstate__(self):
...             return 'foo'
...     def __setstate__(self, state):
...             print state
... 
>>> x = Foo(str)
>>> pickle.loads(pickle.dumps(x))
foo
<Foo object at 0x18be1e0 (__main__+Foo-v3 at 0x194bd90)>

另外,你也可以考虑使用其他的序列化工具,比如 json。这样你可以完全控制序列化的过程,自己定义如何将自定义类进行序列化。而且默认情况下,json 不会有 pickle 的安全问题。

1

通过这种方法,你甚至可以用json来代替pickle,达到你的目的。

下面是一个简单的示例,展示了如何处理一些“无法被pickle化的类型”,比如gtk.ListStore。基本上,你需要做几件事:

  1. 定义一个__reduce__方法,这个方法会返回一个函数和重建实例所需的参数。
  2. 确定你的ListStore的列类型。方法self.get_column_type(0)会返回一个Gtype,所以你需要把它映射回相应的Python类型。我把这个留给你自己去做——在我的示例中,我用了一个小技巧,从第一行的值中获取列类型。
  3. 你的_new_foo函数需要重建这个实例。

示例:

import gtk, os, pickle

def _new_foo(cls, coltypes, rows):
    inst = cls.__new__(cls)
    inst.__init__(*coltypes)
    for row in rows:
        inst.append(row)
    return inst

class foo(gtk.ListStore):

    def __reduce__(self):
        rows = [list(row) for row in self]
        # hack - to be correct you'll really need to use 
        # `self.get_column_type` and map it back to Python's 
        # corresponding type.
        coltypes = [type(c) for c in rows[0]]
        return _new_foo, (self.__class__, coltypes, rows)

x = foo(str, int)
x.append(['foo', 1])
x.append(['bar', 2])

s = pickle.dumps(x)

y = pickle.loads(s)
print list(y[0])
print list(y[1])

输出:

['foo', 1]
['bar', 2]

撰写回答