综合编程

一文了解Python常见的序列化操作

微信扫一扫,分享到朋友圈

一文了解Python常见的序列化操作
0

关于我

编程界的一名小小程序猿,目前在一个创业团队任team lead,技术栈涉及Android、Python、Java和Go,这个也是我们团队的主要技术栈。
联系:hylinux1024@gmail.com

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 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(""foobar"))
""foobar"
>>> 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"')
'"foox08ar'
>>> 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'}
复制代码

阅读原文...


稀土掘金

Adding an Auditing System into a Rest API

上一篇

SpringBoot使用WebSocket

下一篇

您也可能喜欢

评论已经被关闭。

插入图片
一文了解Python常见的序列化操作

长按储存图像,分享给朋友