关于我
一个有思想的程序猿, 终身学习实践者, 目前在一个创业团队任 team lead, 技术栈涉及 Android,Python,Java 和 Go, 这个也是我们团队的主要技术栈.
GitHub: https://github.com/hylinux1024
微信公众号: 终身开发者 (angrycode)
0x00 marshal
marshal 使用的是与 Python 语言相关但与机器无关的二进制来读写 Python 对象的. 这种二进制的格式也跟 Python 语言的版本相关, marshal 序列化的格式对不同的版本的 Python 是不兼容的.
marshal 一般用于 Python 内部对象的序列化.
一般地包括:
基本类型
booleans, integers,floating point numbers,complex numbers
序列集合类型
strings, bytes, bytearray, tuple, list, set, frozenset, dictionary
code 对象 code object
其它类型
None, Ellipsis, StopIteration
marshal 的主要作用是对 Python"编译" 的. pyc 文件读写的支持. 这也是 marshal 对 Python 版本不兼容的原因. 开发者如果要使用序列化 / 反序列化, 那么应该使用 pickle 模块.
常见的方法
marshal.dump(value, file[, version])
序列化一个对象到文件中
marshal.dumps(value[, version])
序列化一个对象并返回一个 bytes 对象
marshal.load(file)
从文件中反序列化一个对象
marshal.loads(bytes)
从 bytes 二进制数据中反序列化一个对象
0x01 pickle
pickle 模块也能够以二进制的方式对 Python 对象进行读写. 相比 marshal 提供基本的序列化能力, pickle 的序列化应用更加广泛.
pickle 序列化后的数据也是与 Python 语言相关的, 即其它语言例如 Java 无法读取由 Python 通过 pickle 序列化的二进制数据. 如果要使用与语言无法的序列化那么我们应该使用 JSON. 下文将会说明.
能被 pickle 序列化的数据类型有:
- None, True, and False
- integers, floating point numbers, complex numbers
- strings, bytes, bytearrays
tuples, lists, sets, and dictionaries 以及包含可以被 pickle 序列化对象
在模块顶层定义的函数对象 (使用 def 定义的, 而不是 lambda 表达式)
在模块顶层定义内置函数
在模式顶层定义的类
一个类的__dict__包含了可序列化的对象或__getstate__() 方法返回了能够被序列化的对象
如果 pickle 一个不支持序列化的对象时将会抛出 PicklingError.
常见的方法
pickle.dump(obj, file, protocol=None, *, fix_imports=True)
将 obj 对象序列化到一个 file 文件中, 该方法与 Pickler(file, protocol).dump(obj) 等价.
pickle.dumps(obj, protocol=None, *, fix_imports=True)
将 obj 对象序列化成 bytes 二进制数据.
pickle.load(file, *, fix_imports=True, encoding="ASCII", errors="strict")
从 file 文件中反序列化一个对象, 该方法与 Unpickler(file).load() 等价.
pickle.loads(bytes_object, *, fix_imports=True, encoding="ASCII", errors="strict")
从二进制数据 bytes_object 反序列化对象.
序列化例子
- import pickle
- # 定义了一个包含了可以被序列化对象的字典
- data = {
- 'a': [1, 2.0, 3, 4 + 6j],
- 'b': ("character string", b"byte string"),
- 'c': {None, True, False}
- }
- with open('data.pickle', 'wb') as f:
- # 序列化对象到一个 data.pickle 文件中
- # 指定了序列化格式的版本 pickle.HIGHEST_PROTOCOL
- pickle.dump(data, f, pickle.HIGHEST_PROTOCOL)
执行之后在文件夹中多一个 data.pickle 文件
serialization
├── data.pickle
├── pickles.py
└── unpickles.py
反序列化例子
- import pickle
- with open('data.pickle', 'rb') as f:
- # 从 data.pickle 文件中反序列化对象
- # pickle 能够自动检测序列化文件的版本
- # 所以这里可以不用版本号
- data = pickle.load(f)
- print(data)
- # 执行后结果
- # {'a': [1, 2.0, 3, (4+6j)], 'b': ('character string', b'byte string'), 'c': {False, True, None}}
- 0x02 JSON
JSON 是与语言无关, 非常通用的数据交互格式. 在 Python 它与 marshal 和 pickle 一样拥有相似的 API.
常见的方法
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)
序列化对象到 fp 文件中
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)
将 obj 序列化成 JSON 对象
JSON.load(fp, *, cls=None, object_hook=None, parse_float=None, parse_int=None, parse_constant=None, object_pairs_hook=None, **kw)
从文件中反序列化成一个对象
JSON.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 与 Python 对象的转化对照表
JSON | Python |
---|---|
object | dict |
list,tuple | array |
str | string |
int, float, int- & float-derived Enums | number |
True | true |
False | false |
None | null |
对于基本类型, 序列, 以及包含基本类型的集合类型 JSON 都可以很好的完成序列化工作.
序列化例子
- >>> import JSON
- >>> JSON.dumps(['foo', {
- 'bar': ('baz', None, 1.0, 2)
- }])
- '["foo", {"bar": ["baz", null, 1.0, 2]}]'
- >>> print(JSON.dumps("\"foo\bar"))
- "\"foo\bar">>> print(JSON.dumps('\u1234'))
- "\u1234"
- >>> print(JSON.dumps('\\'))
- "\\"
- >>> print(JSON.dumps({
- "c": 0, "b": 0, "a": 0
- }, sort_keys=True))
- {
- "a": 0, "b": 0, "c": 0
- }
- >>> from io import StringIO
- >>> io = StringIO()
- >>> JSON.dump(['streaming API'], io)
- >>> io.getvalue()
- '["streaming API"]'
反序列化例子
- >>> import JSON
- >>> JSON.loads('["foo", {"bar":["baz", null, 1.0, 2]}]')
- ['foo', {
- 'bar': ['baz', None, 1.0, 2]
- }]
- >>> JSON.loads('"\\"foo\\bar"')
- '"foo\x08ar'
- >>> from io import StringIO
- >>> io = StringIO('["streaming API"]')
- >>> JSON.load(io)
- ['streaming API']
对于 object 的情况就复杂一些了
例如定义了复数 complex 对象的 JSON 文档
- complex_data.JSON
- {
- "__complex__": true,
- "real": 42,
- "imaginary": 36
- }
要把这个 JSON 文档反序列化成 Python 对象, 就需要定义转化的方法
- # coding=utf-8
- import JSON
- # 定义转化函数, 将 JSON 中的内容转化成 complex 对象
- def decode_complex(dct):
- if "__complex__" in dct:
- return complex(dct["real"], dct["imaginary"])
- else:
- return dct
- if __name__ == '__main__':
- with open("complex_data.json") as complex_data:
- # object_hook 指定转化的函数
- z = JSON.load(complex_data, object_hook=decode_complex)
- print(type(z))
- print(z)
- # 执行结果
- # <class 'complex'>
- # (42+36j)
如果不指定 object_hook, 那么默认将 JSON 文档中的 object 转成 dict
- # coding=utf-8
- import JSON
- if __name__ == '__main__':
- with open("complex_data.json") as complex_data:
- # 这里不指定 object_hook
- z2 = JSON.loads(complex_data.read())
- print(type(z2))
- print(z2)
- # 执行结果
- # <class 'dict'>
- # {'__complex__': True, 'real': 42, 'imaginary': 36}
可以看到 JSON 文档中的 object 转成了 dict 对象.
一般情况下这样使用似乎也没什么问题, 但如果对类型要求很高的场景就需要明确定义转化的方法了.
除了 object_hook 参数还可以使用 JSON.JSONEncoder
- import JSON
- class ComplexEncoder(JSON.JSONEncoder):
- def default(self, obj):
- if isinstance(obj, complex):
- # 如果 complex 对象这里转成数组的形式
- return [obj.real, obj.imag]
- # 默认处理
- return JSON.JSONEncoder.default(self, obj)
- if __name__ == '__main__':
- c = JSON.dumps(2 + 1j, cls=ComplexEncoder)
- print(type(c))
- print(c)
- # 执行结果
- # <class 'str'>
- # [2.0, 1.0]
因为 JSON 模块并不是对所有类型都能够自动完成序列化的, 对于不支持的类型, 会直接抛出 TypeError.
- >>> import datetime
- >>> d = datetime.datetime.now()
- >>> dct = {'birthday':d,'uid':124,'name':'jack'}
- >>> dct
- {'birthday': datetime.datetime(2019, 6, 14, 11, 16, 17, 434361), 'uid': 124, 'name': 'jack'}
- >>> JSON.dumps(dct)
- Traceback (most recent call last):
- File "<pyshell#19>", line 1, in <module>
- JSON.dumps(dct)
- File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/json/__init__.py", line 231, in dumps
- return _default_encoder.encode(obj)
- File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/json/encoder.py", line 199, in encode
- chunks = self.iterencode(o, _one_shot=True)
- File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/json/encoder.py", line 257, in iterencode
- return _iterencode(o, 0)
- File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/json/encoder.py", line 179, in default
- raise TypeError(f'Object of type {o.__class__.__name__}'
- TypeError: Object of type datetime is not JSON serializable
对于不支持序列化的类型例如 datetime 以及自定义类型, 就需要使用 JSONEncoder 来定义转化的逻辑.
- import JSON
- import datetime
- # 定义日期类型的 JSONEncoder
- class DatetimeEncoder(JSON.JSONEncoder):
- def default(self, obj):
- if isinstance(obj, datetime.datetime):
- return obj.strftime('%Y-%m-%d %H:%M:%S')
- elif isinstance(obj, datetime.date):
- return obj.strftime('%Y-%m-%d')
- else:
- return JSON.JSONEncoder.default(self, obj)
- if __name__ == '__main__':
- d = datetime.date.today()
- dct = {"birthday": d, "name": "jack"}
- data = JSON.dumps(dct, cls=DatetimeEncoder)
- print(data)
- # 执行结果
- # {"birthday": "2019-06-14", "name": "jack"}
现在我们希望发序列化时, 能够将 JSON 文档中的日期格式转化成 datetime.date 对象, 这时就需要使用到 JSON.JSONDecoder 了.
- # coding=utf-8
- import JSON
- import datetime
- # 定义 Decoder 解析 JSON
- class DatetimeDecoder(JSON.JSONDecoder):
- # 构造方法
- def __init__(self):
- super().__init__(object_hook=self.dict2obj)
- def dict2obj(self, d):
- if isinstance(d, dict):
- for k in d:
- if isinstance(d[k], str):
- # 对日期格式进行解析, 生成一个 date 对象
- dat = d[k].split("-")
- if len(dat) == 3:
- date = datetime.date(int(dat[0]), int(dat[1]), int(dat[2]))
- d[k] = date
- return d
- if __name__ == '__main__':
- d = datetime.date.today()
- dct = {"birthday": d, "name": "jack"}
- data = JSON.dumps(dct, cls=DatetimeEncoder)
- # print(data)
- obj = JSON.loads(data, cls=DatetimeDecoder)
- print(type(obj))
- print(obj)
- # 执行结果
- # {"birthday": "2019-06-14", "name": "jack"}
- # <class 'dict'>
- # {'birthday': datetime.date(2019, 6, 14), 'name': 'jack'}
0x03 总结一下
Python 常见的序列化工具有 marshal,pickle 和 JSON.marshal 主要用于 Python 的. pyc 文件, 并与 Python 版本相关. 它不能序列化用户定义的类.
pickle 是 Python 对象的序列化工具则比 marshal 更通用些, 它可以兼容 Python 的不同版本. JSON 是一种语言无关的数据结构, 广泛用于各种网络应用尤其在 REST API 的服务中的数据交互.
0x04 学习资料
来源: https://www.cnblogs.com/angrycode/p/11416092.html