Python之数据序列化(json、pickle、shelve)
本节内容
- 前言
- json 模块
- pickle 模块
- shelve 模块
- 总结
一、前言
1. 现实需求
每种编程语言都有各自的数据类型,其中面向对象的编程语言还允许开发者自定义数据类型(如:自定义类),Python 也是一样。很多时候我们会有这样的需求:
- 把内存中的各种数据类型的数据通过网络传送给其它机器或客户端;
- 把内存中的各种数据类型的数据保存到本地磁盘持久化;
2. 数据格式
如果要将一个系统内的数据通过网络传输给其它系统或客户端,我们通常都需要先把这些数据转化为字符串或字节串,而且需要规定一种统一的数据格式才能让数据接收端正确解析并理解这些数据的含义。XML 是早期被广泛使用的数据交换格式,在早期的系统集成论文中经常可以看到它的身影;如今大家使用更多的数据交换格式是JSON(JavaScript Object Notation),它是一种轻量级的数据交换格式。JSON 相对于 XML 而言,更加加单、易于阅读和编写,同时也易于机器解析和生成。除此之外,我们也可以自定义内部使用的数据交换格式。
如果是想把数据持久化到本地磁盘,这部分数据通常只是供系统内部使用,因此数据转换协议以及转换后的数据格式也就不要求是标准、统一的,只要本系统内部能够正确识别即可。但是,系统内部的转换协议通常会随着编程语言版本的升级而发生变化(改进算法、提高效率),因此通常会涉及转换协议与编程语言的版本兼容问题,下面要介绍的 pickle 协议就是这样一个例子。
3. 序列化 / 反序列化
将对象转换为可通过网络传输或可以存储到本地磁盘的数据格式(如:XML、JSON 或特定格式的字节串)的过程称为序列化;反之,则称为反序列化。
4. 相关模块
本节要介绍的就是 Python 内置的几个用于进行数据序列化的模块:
模块名称 | 描述 | 提供的 api
- | - | -
json | 用于实现 Python 数据类型与通用(json)字符串之间的转换 | dumps()、dump()、loads()、load()
pickle | 用于实现 Python 数据类型与 Python 特定二进制格式之间的转换 | dumps()、dump()、loads()、load()
shelve | 专门用于将 Python 数据类型的数据持久化到磁盘,shelve 是一个类似 dict 的对象,操作十分便捷 | open()
二、json 模块
大部分编程语言都会提供处理 json 数据的接口,Python 2.6 开始加入了 json 模块,且把它作为一个内置模块提供,无需下载即可使用。
1. 序列化与反序列化
Python 的 JSON 模块 序列化与反序列化的过程分别叫做:encoding 和 decoding。
- encoding: 把 Python 对象转换成 JSON 字符串
- decoding: 把 JSON 字符串转换成 python 对象
json 模块提供了以下两个方法来进行序列化和反序列化操作:
# 序列化:将 Python 对象转换成 json 字符串
dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, cls=None, indent=None, separators=None, default=None, sort_keys=False, **kw)
# 反序列化:将 json 字符串转换成 Python 对象
loads(s, encoding=None, cls=None, object_hook=None, parse_float=None, parse_int=None, parse_constant=None, object_pairs_hook=None, **kw)
除此之外,json 模块还提供了两个额外的方法允许我们直接将序列化后得到的 json 数据保存到文件中,以及直接读取文件中的 json 数据进行反序列化操作:
# 序列化:将 Python 对象转换成 json 字符串并存储到文件中
dump(obj, fp, skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, cls=None, indent=None, separators=None, default=None, sort_keys=False, **kw)
# 反序列化:读取指定文件中的 json 字符串并转换成 Python 对象
load(fp, cls=None, object_hook=None, parse_float=None, parse_int=None, parse_constant=None, object_pairs_hook=None, **kw)
2. JSON 与 Python 之间数据类型对应关系
Python 转 JSON
Python | JSON
- | -
dict | Object
list, tuple | array
str | string
int, float, int- & float-derived Enums | numbers
True | true
False | false
None | null
JSON 转 Python
JSON | Python
- | -
object | dict
array | list
string | str
number(int) | int
number(real) | float
true | True
false | False
null | None
说明:
- Python dict 中的非字符串 key 被转换成 JSON 字符串时都会被转换为小写字符串;
- Python 中的 tuple,在序列化时会被转换为 array,但是反序列化时,array 会被转化为 list;
- 由以上两点可知,当 Python 对象中包含 tuple 数据或者包含 dict,且 dict 中存在非字符串的 key 时,反序列化后得到的结果与原来的 Python 对象是不一致的;
- 对于 Python 内置的数据类型(如:str, unicode, int, float, bool, None, list, tuple, dict)json 模块可以直接进行序列化 / 反序列化处理;对于自定义类的对象进行序列化和反序列化时,需要我们自己定义一个方法来完成定义 object 和 dict 之间进行转化。
3. 实例:内置数据类型序列化 / 反序列化
序列化
# 序列化
>>> json.dumps({'a':'str', 'c': True, 'e': 10, 'b': 11.1, 'd': None, 'f': [1, 2, 3], 'g':(4, 5, 6)})
'{"a": "str", "c": true, "b": 11.1, "e": 10, "d": null, "g": [4, 5, 6], "f": [1, 2, 3]}'
sort_keys 参数: 表示序列化时是否对 dict 的 key 进行排序(dict 默认是无序的)
# 序列化并对key进行排序
>>> json.dumps({'a':'str', 'c': True, 'e': 10, 'b': 11.1, 'd': None, 'f': [1, 2, 3], 'g':(4, 5, 6)}, sort_keys=True)
'{"a": "str", "b": 11.1, "c": true, "d": null, "e": 10, "f": [1, 2, 3], "g": [4, 5, 6]}'
indent 参数: 表示缩进的意思,它可以使得数据存储的格式变得更加优雅、可读性更强;如果 indent 是一个非负整数或字符串,则 JSON array 元素和 object 成员将会被以相应的缩进级别进行打印输出;如果 indent 是 0 或负数或空字符串,则将只会插入换行,不会有缩进。
# 序列化并对 key 进行排序及格式化输出
>>> print(json.dumps({'a':'str', 'c': True, 'e': 10, 'b': 11.1, 'd': None, 'f': [1, 2, 3], 'g':(4, 5, 6)}, sort_keys=True, indent=4))
{
"a": "str",
"b": 11.1,
"c": true,
"d": null,
"e": 10,
"f": [
1,
2,
3
],
"g": [
4,
5,
6
]
}
separators 参数: 尽管 indent 参数可以使得数据存储的格式变得更加优雅、可读性更强,但是那是通过添加一些冗余的空白字符进行填充的。当 json 被用于网络数据通信时,应该尽可能的减少无用的数据传输,这样可以节省带宽并加快数据传输速度。json 模块序列化 Python 对象后得到的 json 字符串中的 ',' 号和 ':' 号分隔符后默认都会附加一个空白字符,我们可以通过 separators 参数重新指定分隔符,从而去除无用的空白字符;
- 该参数的值应该是一个 tuple(item_separator, key_separator)
- 如果 indent 是 None,其默认值为 (',', ':')
- 如果 indent 不为 None,则默认值为 (',', ':')
- 我们可以通过为 separator 赋值为 (',', ':') 来消除空白字符
>>> json.dumps({'a':'str', 'c': True, 'e': 10, 'b': 11.1, 'd': None, 'f': [1, 2, 3], 'g':(4, 5, 6)})
'{"a": "str", "c": true, "b": 11.1, "e": 10, "d": null, "g": [4, 5, 6], "f": [1, 2, 3]}'
>>> json.dumps({'a':'str', 'c': True, 'e': 10, 'b': 11.1, 'd': None, 'f': [1, 2, 3], 'g':(4, 5, 6)}, separators=(',',':'))
'{"a":"str","c":true,"b":11.1,"e":10,"d":null,"g":[4,5,6],"f":[1,2,3]}'
ensure_ascii 参数: 当该参数的值为 True(默认值)时,输出中的所有非 ASCII 字符(比如中文)都会被转义成 '\uXXXX' 组成的序列,得到的结果是一个完全由 ASCII 字符组成的 str 实例。如果我们想得到一个人类可读的输出结果,需要把 ensure_ascii 参数的值设置为 False。
>>> stu={"name": "小明", "age" : 16}
>>> stu_json = json.dumps(stu)
>>> print(stu_json)
'{"name": "\u5c0f\u660e", "age": 16}'
>>> stu_json01 = json.dumps(stu, ensure_ascii=False)
>>> print(stu_json01)
'{"name": "小明", "age": 16}'
说明: 实际上 '\uXXXX' 是 Unicode 字符对应的内存编码值,该内存编码名称为 "unicode-escape", 我们可以通过
unicodestr.encode('unicode-escape')
和decode('unicode-escape')
来完成 Unicode 字符串与 Unicode 内存编码序列进行相互转换,如下所示:
>>> str1 = "hello 中国"
>>> str2 = str1.encode("unicode_escape")
>>> print(str2)
b'hello \\u4e2d\\u56fd'
>>> str3 = str2.decode("unicode_escape")
>>> print(str3)
hello 中国
注意 str2 是字节串,不是字符串,因此 \u 前面需要再加一个反斜线做转义。我们把 str2 转换成字符串就是我们熟悉的格式了:
>>> str4=str2.decode("utf-8")
>>> print(str4)
hello \u4e2d\u56fd
>>>
反序列化
# 反序列化
>>> json.loads('{"a": "str", "c": true, "b": 11.1, "e": 10, "d": null, "g": [4, 5, 6], "f": [1, 2, 3]}')
{'c': True, 'e': 10, 'a': 'str', 'g': [4, 5, 6], 'd': None, 'f': [1, 2, 3], 'b': 11.1}
>>> json.loads('{"a":"str","c":true,"b":11.1,"e":10,"d":null,"g":[4,5,6],"f":[1,2,3]}')
{'c': True, 'e': 10, 'a': 'str', 'g': [4, 5, 6], 'd': None, 'f': [1, 2, 3], 'b': 11.1}
dump()与 load() 函数示例
# 序列化到文件中
>>> with open('test.json', 'w') as fp:
... json.dump({'a':'str 中国', 'c': True, 'e': 10, 'b': 11.1, 'd': None, 'f': [1, 2, 3], 'g':(4, 5, 6)}, fp, indent=4)
# 反序列化文件中的内容
>>> with open('test.json', 'r') as fp:
... json.load(fp)
{'e': 10, 'g': [4, 5, 6], 'b': 11.1, 'c': True, 'd': None, 'a': 'str 中国', 'f': [1, 2, 3]}
需要说明的是: 如果试图使用相同的 fp 重复调用 dump()函数去序列化多个对象(或序列化同一个对象多次),将会产生一个无效的 JSON 文件,也就是说对于一个 fp 只能调用一次 dump()。
4. 实例:自定义数据类型的序列化 / 反序列化
Python 是面向对象的编程语言,我们可以自定义需要的数据类型;实际工作中,我们常常会用到自定义数据类型的序列化与反序列化操作。要实现自定义数据类型的序列化与反序列化有两种方式:
- 通过转换函数实现
- 通过继承 JSONEncoder 和 JSONDecoder 类实现
首先来自定义一个数据类型
class Student(object):
def __init__(self, name, age, sno):
self.name = name
self.age = age
self.sno = sno
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__repr__</span><span class="hljs-params">(<span class="hljs-keyword">self</span>)</span></span>:
<span class="hljs-keyword">return</span> <span class="hljs-string">'Student [name: %s, age: %d, sno: %d]'</span> % (<span class="hljs-keyword">self</span>.name, <span class="hljs-keyword">self</span>.age, <span class="hljs-keyword">self</span>.sno)
直接调用 dumps() 方法会引发 TypeError 错误:
>>> stu = Student('Tom', 19, 1)
>>> print(stu)
Student [name: Tom, age: 19, sno: 1]
>>>
>>> json.dumps(stu)
...
TypeError: Student [name: Tom, age: 19, sno: 1] is not JSON serializable
上面的异常信息中指出:stu 对象不可以被序列化为 JSON 格式的数据。那么我们分别通过“编写转换函数” 和 “继承 JSONEncoder 和 JSONDecoder 类” 来实现对这个自定义数据类型的 JSON 序列化和反序列化。
方法 1:编写转换函数
那么这个转换函数要完成哪两个数据类型之间的转换呢? 从上面列出的 JSON 与 Python 数据类型的对应表中可知,JSON 中的 object 对应的是 Python 中的 dict,因此要对 Python 中的自定义数据类型的对象进行序列化,就需要先把这个对象转换成 json 模块可以直接进行序列化 dict 类型。由此可知,这个转换函数是要完成的是 Python 对象(不是 JSON 对象)与 dict 之间的相互转换,且序列化时转换过程是“Python 对象 --> dict --> JSON object”,反序列化的过程是“JSON object -> dict --> Python 对象”。所以,我们需要编写两个转换函数来分别实现序列化和反序列化时的转换过程。
def obj2dict(obj):
d = {}
d['__class__'] = obj.__class__.__name__
d['__module__'] = obj.__module__
d.update(obj.__dict__)
return d
def dict2obj(d):
if 'class' in d:
class_name = d.pop('class')
module_name = d.pop('module')
module = import(module_name)
class_ = getattr(module, class_name)
args = dict((key.encode('ascii'), value) for key, value in d.items())
instance = class_(**args)
else:
instance = d
return instance
序列化测试:
>>> import json
>>> obj2dict(stu)
{'sno': 1, 'module': 'main', 'age': 19, 'class': 'Student', 'name': 'Tom'}
>>> json.dumps(obj2dict(stu))
'{"sno": 1, "module": "main", "age": 19, "class": "Student", "name": "Tom"}'
>>> json.dumps(stu, default=obj2dict)
'{"sno": 1, "module": "main", "age": 19, "class": "Student", "name": "Tom"}'
json.dumps(stu, default=obj2dict)
等价于 json.dumps(obj2dict(stu))
反序列化测试:
>>> json.loads('{"sno": 1,"__module__":"__main__","age": 19,"__class__":"Student","name":"Tom"}')
{u'sno': 1, u'__module__': u'__main__', u'age': 19, u'name': u'Tom', u'__class__': u'Student'}
>>> dict2obj(json.loads('{"sno": 1,"module":"main","age": 19,"class":"Student","name":"Tom"}'))
Student [name: Tom, age: 19, sno: 1]
>>> json.loads('{"sno": 1,"module":"main","age": 19,"class":"Student","name":"Tom"}', object_hook=dict2obj)
Student [name: Tom, age: 19, sno: 1]
json.loads(JSON_STR, object_hook=dict2obj)
等价于 dict2obj(json.loads(JSON_STR))
方法 2:继承 JSONEncoder 和 JSONDecoder 实现子类
import json
class MyJSONEncoder(json.JSONEncoder):
def default(self, obj):
d = {}
d['class'] = obj.class.name
d['module'] = obj.module
d.update(obj.dict)
return d
class MyJSONDecoder(json.JSONDecoder):
def init(self):
json.JSONDecoder.init(self, object_hook=self.dict2obj)
<span class="hljs-keyword">def</span> <span class="hljs-title function_">dict2obj</span>(<span class="hljs-params">self, d</span>):
<span class="hljs-keyword">if</span> <span class="hljs-string">'__class__'</span> <span class="hljs-keyword">in</span> d:
class_name = d.pop(<span class="hljs-string">'__class__'</span>)
module_name = d.pop(<span class="hljs-string">'__module__'</span>)
module = <span class="hljs-built_in">__import__</span>(module_name)
class_ = <span class="hljs-built_in">getattr</span>(module, class_name)
args = <span class="hljs-built_in">dict</span>((key.encode(<span class="hljs-string">'ascii'</span>), value) <span class="hljs-keyword">for</span> key, value <span class="hljs-keyword">in</span> d.items())
instance = class_(**args)
<span class="hljs-keyword">else</span>:
instance = d
<span class="hljs-keyword">return</span> instance
序列化测试:
>>> stu = Student('Tom', 19, 1)
方式一:直接调用子类 MyJSONEncoder 的encode() 方法进行序列化
>>> MyJSONEncoder().encode(stu)
'{"class": "Student", "module": "main", "name": "Tom", "age": 19, "sno": 1}'
>>> MyJSONEncoder(separators=(',', ':')).encode(stu)
'{"class":"Student","module":"main","name":"Tom","age":19,"sno":1}'
方式二:将子类 MyJSONEncoder 作为 cls 参数的值传递给 json.dumps() 函数
>>> json.dumps(stu, cls=MyJSONEncoder)
'{"class": "Student", "module": "main", "name": "Tom", "age": 19, "sno": 1}'
>>> json.dumps(stu, cls=MyJSONEncoder, separators=(',', ':'))
'{"class":"Student","module":"main","name":"Tom","age":19,"sno":1}'
反序列化测试:
>>> MyJSONDecoder().decode('{"sno": 1, "__module__": "__main__", "age": 19, "__class__": "Student", "name": "Tom"}')
Student [name: Tom, age: 19, sno: 1]
说明: 经过测试发现
MyJSONDecoder().decode(JSON_STR)
和json.loads(JSON_STR, object_hook=dict2obj)
只能在 Python 2.7 上正确执行,在 Python 3.5 上无法正确执行;而json.loads(JSON_STR, cls=MyJSONDecoder)
无论在 Python 2.7 还是在 Python 3.5 上都无法正确执行。这说明 json 模块对于自定义数据类型的反序列化支持还是比较有限的,但是我们也可以通过 json.loads(JSON_STR) 函数,不指定 cls 参数来得到一个 dict 对象,然后自己完成 dict 到 object 的转换。
继承 JSONEncoder 实现序列化时还有一个额外的作用,就是可以通过 iterencode() 方法把一个很大的数据对象分多次进行序列化,这对于网络传输、磁盘持久化等情景非常有用。
>>> for chunk in MyJSONEncoder().iterencode(stu):
... print(chunk)
...
{
"__class__"
:
"Student"
,
"name"
:
"Tom"
,
"__module__"
:
"__main__"
,
"sno"
:
1
,
"age"
:
19
}
大数据对象序列化网络传输伪代码:
for chunk in JSONEncoder().iterencode(bigobject):
mysocket.write(chunk)
三、pickle 模块
pickle 模块实现了用于对 Python 对象结构进行 序列化 和 反序列化 的二进制协议,与 json 模块不同的是 pickle 模块序列化和反序列化的过程分别叫做 pickling 和 unpickling:
- pickling: 是将 Python 对象转换为字节流的过程;
- unpickling: 是将字节流二进制文件或字节对象转换回 Python 对象的过程;
1. pickle 模块与 json 模块对比
- JSON 是一种文本序列化格式(它输出的是 unicode 文件,大多数时候会被编码为 utf-8),而 pickle 是一个二进制序列化格式;
- JOSN 是我们可以读懂的数据格式,而 pickle 是二进制格式,我们无法读懂;
- JSON 是与特定的编程语言或系统无关的,且它在 Python 生态系统之外被广泛使用,而 pickle 使用的数据格式是特定于 Python 的;
- 默认情况下,JSON 只能表示 Python 内建数据类型,对于自定义数据类型需要一些额外的工作来完成;pickle 可以直接表示大量的 Python 数据类型,包括自定数据类型(其中,许多是通过巧妙地使用 Python 内省功能自动实现的;复杂的情况可以通过实现 specific object API 来解决)
2. pickle 模块使用的数据流格式
上面提到,pickle 使用的数据格式是特定于 Python 的。这使得它不受诸如 JSON 或 XDR 的外部标准限值,但是这也意味着非 Python 程序可能无法重建 pickled Python 对象。默认情况下,pickle 数据格式使用相对紧凑的二进制表示。如果需要最佳大小特征,可以有效的压缩 pickled 数据。pickletools 模块包含可以用于对 pickle 生成的数据流进行分析的工具。目前有 5 种不同的协议可以用于 pickle。使用的协议越高,就需要更新的 Python 版本去读取 pickle 产生的数据:
- 协议 v0 是原始的“人类可读”协议,并且向后兼容早期的 Python 版本;
- 协议 v1 是一个旧的二进制格式,也与早期版本的 Python 兼容;
- 协议 v2 在 Python 2.3 中引入,它提供更高效的 pickling;
- 协议 v3 是在 Python 3.0 添加的协议,它明确支持 bytes 对象,且不能被 Python 2.x 进行 unpickle 操作;这是默认协议,也是当需要兼容其他 Python 3 版本时被推荐使用的协议;
- 协议 4 是在 Python 3.4 添加的协议,它添加了对极大对象的支持,pickling 更多种类的对象,以及一些数据格式的优化。
说明: Python 2.x 中默认使用的是协议 v0,如果协议指定为赋值或 HIGHEST_PROTOCOL,将使用当前可用的最高协议版本;Python 3.x 中默认使用的是协议 v3,它兼容其他 Python 3 版本,但是不兼容 Python 2。
注意: 序列化(Serialization)是一个比持久化(Persistence)更加原始的概念;虽然
pickle
可以读写文件对象,但是它不处理持久化对象的命名问题,也不处理对持久化对象的并发访问问题(甚至更复杂的问题)。pickle
模块可以将复杂对象转换为字节流,并且可以将字节流转换为具有相同内部结构的对象。或许最可能对这些字节流做的事情是将它们写入文件,但是也可以对它们进行网络传输或将它们存储在数据库中。shelve
模块提供了一个简单的接口用于在 DBM 风格的数据库文件上对对象进行 pickle 和 unpickle 操作。
3. pickle 模块提供的相关函数
pickle 模块提供的几个序列化 / 反序列化的函数与 json 模块基本一致:
# 将指定的 Python 对象通过 pickle 序列化作为 bytes 对象返回,而不是将其写入文件
dumps(obj, protocol=None, *, fix_imports=True)
# 将通过 pickle 序列化后得到的字节对象进行反序列化,转换为 Python 对象并返回
loads(bytes_object, *, fix_imports=True, encoding="ASCII", errors="strict")
# 将指定的 Python 对象通过 pickle 序列化后写入打开的文件对象中,等价于Pickler(file, protocol).dump(obj)
dump(obj, file, protocol=None, *, fix_imports=True)
# 从打开的文件对象中读取 pickled 对象表现形式并返回通过 pickle 反序列化后得到的 Python 对象
load(file, *, fix_imports=True, encoding="ASCII", errors="strict")
说明: 上面这几个方法参数中,* 号后面的参数都是 Python 3.x 新增的,目的是为了兼容 Python 2.x,具体用法请参看官方文档。
4. 实例:内置数据类型的序列化 / 反序列化
Python 2.x
>>> import pickle
>>>
>>> var_a = {'a':'str', 'c': True, 'e': 10, 'b': 11.1, 'd': None, 'f': [1, 2, 3], 'g':(4, 5, 6)}
# 序列化
>>> var_b = pickle.dumps(var_a)
>>> var_b
"(dp0\nS'a'\np1\nS'str'\np2\nsS'c'\np3\nI01\nsS'b'\np4\nF11.1\nsS'e'\np5\nI10\nsS'd'\np6\nNsS'g'\np7\n(I4\nI5\nI6\ntp8\nsS'f'\np9\n(lp10\nI1\naI2\naI3\nas."
# 反序列化
>>> var_c = pickle.loads(var_b)
>>> var_c
{'a': 'str', 'c': True, 'b': 11.1, 'e': 10, 'd': None, 'g': (4, 5, 6), 'f': [1, 2, 3]}
Python 3.x
>>> import pickle
>>>
>>> var_a = {'a':'str', 'c': True, 'e': 10, 'b': 11.1, 'd': None, 'f': [1, 2, 3], 'g':(4, 5, 6)}
# 序列化
>>> var_b = pickle.dumps(var_a)
>>> var_b
b'\x80\x03}q\x00(X\x01\x00\x00\x00eq\x01K\nX\x01\x00\x00\x00aq\x02X\x03\x00\x00\x00strq\x03X\x01\x00\x00\x00fq\x04]q\x05(K\x01K\x02K\x03eX\x01\x00\x00\x00gq\x06K\x04K\x05K\x06\x87q\x07X\x01\x00\x00\x00bq\x08G@&333333X\x01\x00\x00\x00cq\t\x88X\x01\x00\x00\x00dq\nNu.'
# 反序列化
>>> var_c = pickle.loads(var_b)
>>> var_c
{'e': 10, 'a': 'str', 'f': [1, 2, 3], 'g': (4, 5, 6), 'b': 11.1, 'c': True, 'd': None}
dump()与 load()
>>> import pickle
>>>
>>> var_a = {'a':'str', 'c': True, 'e': 10, 'b': 11.1, 'd': None, 'f': [1, 2, 3], 'g':(4, 5, 6)}
持久化到文件
>>> with open('pickle.txt', 'wb') as f:
... pickle.dump(var_a, f)
...
从文件中读取数据
>>> with open('pickle.txt', 'rb') as f:
... var_b = pickle.load(f)
...
>>> var_b
{'e': 10, 'a': 'str', 'f': [1, 2, 3], 'g': (4, 5, 6), 'b': 11.1, 'c': True, 'd': None}
>>>
说明:
- 默认情况下 Python 2.x 中 pickled 后的数据是字符串形式,需要将它转换为字节对象才能被 Python 3.x 中的 pickle.loads()反序列化;Python 3.x 中 pickling 所使用的协议是 v3,因此需要在调用 pickle.dumps() 时指定可选参数 protocol 为 Python 2.x 所支持的协议版本(0,1,2),否则 pickled 后的数据不能被被 Python 2.x 中的 pickle.loads() 反序列化;
- Python 3.x 中 pickle.dump()和 pickle.load() 方法中指定的文件对象,必须以二进制模式打开,而 Python 2.x 中可以以二进制模式打开,也可以以文本模式打开。
5. 实例:自定义数据类型的序列化 / 反序列化
首先来自定义一个数据类型:
class Student(object):
def __init__(self, name, age, sno):
self.name = name
self.age = age
self.sno = sno
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__repr__</span><span class="hljs-params">(<span class="hljs-keyword">self</span>)</span></span>:
<span class="hljs-keyword">return</span> <span class="hljs-string">'Student [name: %s, age: %d, sno: %d]'</span> % (<span class="hljs-keyword">self</span>.name, <span class="hljs-keyword">self</span>.age, <span class="hljs-keyword">self</span>.sno)
pickle 模块可以直接对自定数据类型进行序列化 / 反序列化操作,无需编写额外的处理函数或类。
>>> stu = Student('Tom', 19, 1)
>>> print(stu)
Student [name: Tom, age: 19, sno: 1]
序列化
>>> var_b = pickle.dumps(stu)
>>> var_b
b'\x80\x03c__main__\nStudent\nq\x00)\x81q\x01}q\x02(X\x04\x00\x00\x00nameq\x03X\x03\x00\x00\x00Tomq\x04X\x03\x00\x00\x00ageq\x05K\x13X\x03\x00\x00\x00snoq\x06K\x01ub.'
反序列化
>>> var_c = pickle.loads(var_b)
>>> var_c
Student [name: Tom, age: 19, sno: 1]
持久化到文件
>>> with open('pickle.txt', 'wb') as f:
... pickle.dump(stu, f)
...
从文件总读取数据
>>> with open('pickle.txt', 'rb') as f:
... pickle.load(f)
...
Student [name: Tom, age: 19, sno: 1]
四、shelve 模块
shelve 是一个简单的数据存储方案,类似 key-value 数据库,可以很方便的保存 python 对象,其内部是通过 pickle 协议来实现数据序列化。shelve 只有一个 open() 函数,这个函数用于打开指定的文件(一个持久的字典),然后返回一个 shelf 对象。shelf 是一种持久的、类似字典的对象。它与“dbm”的不同之处在于,其 values 值可以是任意基本 Python 对象 --pickle 模块可以处理的任何数据。这包括大多数类实例、递归数据类型和包含很多共享子对象的对象。keys 还是普通的字符串。
open(filename, flag='c', protocol=None, writeback=False)
flag 参数表示打开数据存储文件的格式,可取值与dbm.open()
函数一致:
值 | 描述 |
---|---|
'r' | 以只读模式打开一个已经存在的数据存储文件 |
'w' | 以读写模式打开一个已经存在的数据存储文件 |
'c' | 以读写模式打开一个数据存储文件,如果不存在则创建 |
'n' | 总是创建一个新的、空数据存储文件,并以读写模式打开 |
protocol 参数表示序列化数据所使用的协议版本,默认是 pickle v3;
writeback 参数表示是否开启回写功能。
我们可以把 shelf 对象当 dict 来使用 -- 存储、更改、查询某个 key 对应的数据,当操作完成之后,调用 shelf 对象的 close()函数即可。当然,也可以使用上下文管理器(with 语句),避免每次都要手动调用 close() 方法。
实例:内置数据类型操作
# 保存数据
with shelve.open('student') as db:
db['name'] = 'Tom'
db['age'] = 19
db['hobby'] = ['篮球', '看电影', '弹吉他']
db['other_info'] = {'sno': 1, 'addr': 'xxxx'}
# 读取数据
with shelve.open('student') as db:
for key,value in db.items():
print(key, ':', value)
输出结果:
name : Tom
age : 19
hobby : ['篮球', '看电影', '弹吉他']
other_info : {'sno': 1, 'addr': 'xxxx'}
实例:自定义数据类型操作
# 自定义 class
class Student(object):
def __init__(self, name, age, sno):
self.name = name
self.age = age
self.sno = sno
<span class="hljs-keyword">def</span> <span class="hljs-title function_">__repr__</span>(<span class="hljs-params">self</span>):
<span class="hljs-keyword">return</span> <span class="hljs-string">'Student [name: %s, age: %d, sno: %d]'</span> % (self.name, self.age, self.sno)
# 保存数据
tom = Student('Tom', 19, 1)
jerry = Student('Jerry', 17, 2)
with shelve.open("stu.db") as db:
db['Tom'] = tom
db['Jerry'] = jerry
# 读取数据
with shelve.open("stu.db") as db:
print(db['Tom'])
print(db['Jerry'])
输出结果:
Student [name: Tom, age: 19, sno: 1]
Student [name: Jerry, age: 17, sno: 2]
五、总结
1. 对比
json 模块常用于编写 web 接口,将 Python 数据转换为通用的 json 格式传递给其它系统或客户端;也可以用于将 Python 数据保存到本地文件中,缺点是明文保存,保密性差。另外,如果需要保存非内置数据类型需要编写额外的转换函数或自定义类。
pickle 模块和 shelve 模块由于使用其特有的序列化协议,其序列化之后的数据只能被 Python 识别,因此只能用于 Python 系统内部。另外,Python 2.x 和 Python
3.x 默认使用的序列化协议也不同,如果需要互相兼容需要在序列化时通过 protocol 参数指定协议版本。除了上面这些缺点外,pickle 模块和 shelve 模块相对于 json 模块的优点在于对于自定义数据类型可以直接序列化和反序列化,不需要编写额外的转换函数或类。
shelve 模块可以看做是 pickle 模块的升级版,因为 shelve 使用的就是 pickle 的序列化协议,但是 shelve 比 pickle 提供的操作方式更加简单、方便。shelve 模块相对于其它两个模块在将 Python 数据持久化到本地磁盘时有一个很明显的优点就是,它允许我们可以像操作 dict 一样操作被序列化的数据,而不必一次性的保存或读取所有数据。
2. 建议
- 需要与外部系统交互时用 json 模块;
- 需要将少量、简单 Python 数据持久化到本地磁盘文件时可以考虑用 pickle 模块;
- 需要将大量 Python 数据持久化到本地磁盘文件或需要一些简单的类似数据库的增删改查功能时,可以考虑用 shelve 模块。
3. 附录
要实现的功能 | 可以使用的 api |
---|---|
将 Python 数据类型转换为(json)字符串 | json.dumps() |
将 json 字符串转换为 Python 数据类型 | json.loads() |
将 Python 数据类型以 json 形式保存到本地磁盘 | json.dump() |
将本地磁盘文件中的 json 数据转换为 Python 数据类型 | json.load() |
将 Python 数据类型转换为 Python 特定的二进制格式 | pickle.dumps() |
将 Python 特定的的二进制格式数据转换为 Python 数据类型 | pickle.loads() |
将 Python 数据类型以 Python 特定的二进制格式保存到本地磁盘 | pickle.dump() |
将本地磁盘文件中的 Python 特定的二进制格式数据转换为 Python 数据类型 | pickle.load() |
以类型 dict 的形式将 Python 数据类型保存到本地磁盘或读取本地磁盘数据并转换为数据类型 | shelve.open() |
问题交流群:666948590