Python 序列化自定义的类

Python 序列化自定义的类

目前主要的序列化模块是pickle和json。

接下来,我们把之前的我们自定义的树节点TreeNode类进行序列化。

TreeNode类代码参见Python 数据结构 tree 树

Pickle模块

pickle 可能是使用最多的序列化模块了。
cPickle是它的 C 语言实现,相比具有较好的性能。(推荐优先使用 cPickle)

pickle提供的接口

  • dump() 序列化对象到文件
  • dumps() 序列化对象,返回字符串
  • load() 从文件加载对象
  • loads() 从字符串加载对象

由于pickle支持的数据类型广泛

  • 如数字、布尔值、字符串
  • 只包含可序列化对象的元组、字典、列表等,非嵌套的函数、类
  • 通过类的__dict__或者__getstate__()可以返回序列化对象的实例

不能支持的数据类型

  • 如 sockets 、文件、数据库连接等 (可以通过__getstate__()__setstate__()进行手动操作)

由于我们的TreeNode类内部的属性都是简单的对象,所以可以使用pickle直接进行序列化。

# build a tree
root = TreeNode('') # root name is ''
a1 = root.add_child('a1')
a1.add_child('b1')
a1.add_child('b2')
a2 = root.add_child('a2')
b3 = a2.add_child('b3')
b3.add_child('c1')
root.dump()

# serialize to a string
s = pickle.dumps(root)
print(s)

# deserialize from the string
new_root = pickle.loads(s)
new_root.dump()

可以看到new_root.dump()的结果和之前的root.dump()一样。

但是序列化的结果并不是易于阅读和手工修改, 例如:

(i__main__
TreeNode
p1
(dp2
S'name'
p3
V
sS'parent'
p4
NsS'child'
p5
(dp6
Va1
p7
(i__main__
TreeNode
......

如果包含不能序列化的对象:
- 序列化的时候,需要用到__getstate__()手工剔除
- 反序列化的时候,通过__setstate__()手工加载

例如:假设我们的TreeNode有一个文件句柄属性fp

class TreeNode():
    """The basic node of tree structure"""

    def __init__(self, name, parent=None):
        # super(TreeNode, self).__init__()
        self.name = name
        self.parent = parent
        self.child = {}
        self.fpath = None 
        self.fp = None  # attr can not be serialized

    def __getstate__(self):
        """return a dict for current status"""
        state = self.__dict__.copy()
        del state['fp']  # manually delete
        return state

    def __setstate__(self, state):
        """return a dict for current status"""
        self.__dict__.update(state)
        self.fp = open(self.fpath)  # manually update
        return state

上面的__getstate__()__setstate__()会在pickle的dump()load()中被自动调用。

Json

JSON ( JavaScript Object Notation )是一种轻量级数据交换格式。

由于其格式使用了其他许多流行编程的约定,如 C 、 C++ 、 C#、 Java 、 JS 、 Python 等,加之其简单灵活、可读性和互操作性较强、易于解析和使用等特点,逐渐变得流行起来。

由于TreeNode类是我们自定义的类结构,这里需要我们自己提供用于编码的解码的类。

try: import simplejson as json
except ImportError: import json

class TreeNodeEncoder(json.JSONEncoder):

    def default(self, obj):
        if isinstance(obj, TreeNode):
            return {
                'treenode_name': obj.name,
                'child': {key: val for key, val in obj.items() },        
            }
        return json.JSONEncoder.default(self, obj)


def TreeNodeDecoder(obj):
    if isinstance(obj, dict) and 'treenode_name' in obj:
        newobj = TreeNode(obj['treenode_name'])
        for name, subobj in obj.get('child', {}).items():
            newobj.add_child(name, TreeNodeDecoder(subobj))
        return newobj
    return obj

NOTE: 为什么在上面的代码里用treenode_name这种丑陋的名字,而不是用name
因为在Decoder的时候,需要判断那个object是我们的TreeNode dict。好无奈#_#

当我们需要序列化的时候,

print('test json dumps()')
s = json.dumps(root, cls=TreeNodeEncoder, indent=4, sort_keys=True)
print(s)

print('test json loads()')
new_root = json.loads(s, object_hook=TreeNodeDecoder)
new_root.dump()

序列化时通过参数cls来指定自定义的编码类,反序列化的时候通过object_hook来指定object的处理函数。

序列化时参数indentsort_keys可以使序列化后的字符串更易于阅读和查找,例如:

{
    "child": {
        "a1": {
            "child": {
                "b1": {
                    "child": {},
                    "treenode_name": "b1" },
                "b2": {
                    "child": {},
                    "treenode_name": "b2" }
            },
            "treenode_name": "a1"
        },
        "a2": {
            "child": {
                "b3": {
                    "child": { "c1": { "child": {}, "treenode_name": "c1" } },
                    "treenode_name": "b3" }
            },
            "treenode_name": "a2"
        }
    },
    "treenode_name": ""
}

总结

  1. pickle比json要强大,要快,使用简单
  2. json的序列化结果更易于阅读和手工修改

你可能感兴趣的:(Python)